fmsg: produce all output through fmsg
test / test (push) Successful in 17s Details

The behaviour of print functions from package fmt is not thread safe. Functions provided by fmsg wrap around Logger methods. This makes prefix much cleaner and makes it easy to deal with future changes to logging.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
This commit is contained in:
Ophestra Umiker 2024-10-21 20:47:02 +09:00
parent 380d1f4585
commit 42e0b168e3
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
26 changed files with 194 additions and 280 deletions

View File

@ -9,6 +9,7 @@ import (
"git.ophivana.moe/security/fortify/dbus"
"git.ophivana.moe/security/fortify/internal"
"git.ophivana.moe/security/fortify/internal/app"
"git.ophivana.moe/security/fortify/internal/fmsg"
"git.ophivana.moe/security/fortify/internal/system"
)
@ -60,7 +61,7 @@ func init() {
func tryTemplate() {
if printTemplate {
if s, err := json.MarshalIndent(app.Template(), "", " "); err != nil {
fatalf("cannot generate template: %v", err)
fmsg.Fatalf("cannot generate template: %v", err)
panic("unreachable")
} else {
fmt.Println(string(s))
@ -77,10 +78,10 @@ func loadConfig() *app.Config {
// config from file
c := new(app.Config)
if f, err := os.Open(confPath); err != nil {
fatalf("cannot access config file '%s': %s\n", confPath, err)
fmsg.Fatalf("cannot access config file %q: %s", confPath, err)
panic("unreachable")
} else if err = json.NewDecoder(f).Decode(&c); err != nil {
fatalf("cannot parse config file '%s': %s\n", confPath, err)
fmsg.Fatalf("cannot parse config file %q: %s", confPath, err)
panic("unreachable")
} else {
return c
@ -110,7 +111,7 @@ func configFromFlags() (config *app.Config) {
config.Confinement.SessionBus = dbus.NewConfig(dbusID, true, mpris)
} else {
if c, err := dbus.NewConfigFromFile(dbusConfigSession); err != nil {
fatalf("cannot load session bus proxy config from %q: %s\n", dbusConfigSession, err)
fmsg.Fatalf("cannot load session bus proxy config from %q: %s", dbusConfigSession, err)
} else {
config.Confinement.SessionBus = c
}
@ -119,7 +120,7 @@ func configFromFlags() (config *app.Config) {
// system bus proxy is optional
if dbusConfigSystem != "nil" {
if c, err := dbus.NewConfigFromFile(dbusConfigSystem); err != nil {
fatalf("cannot load system bus proxy config from %q: %s\n", dbusConfigSystem, err)
fmsg.Fatalf("cannot load system bus proxy config from %q: %s", dbusConfigSystem, err)
} else {
config.Confinement.SystemBus = c
}

View File

@ -3,7 +3,6 @@ package main
import (
"errors"
"fmt"
"os"
"git.ophivana.moe/security/fortify/internal/app"
"git.ophivana.moe/security/fortify/internal/fmsg"
@ -12,13 +11,13 @@ import (
func logWaitError(err error) {
var e *fmsg.BaseError
if !fmsg.AsBaseError(err, &e) {
fmt.Println("fortify: 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
fmt.Print("fortify: " + e.Message())
fmsg.Print(e.Message())
} else {
// inner error are either unwrapped store errors
// or joined errors returned by *appSealTx revert
@ -26,7 +25,7 @@ func logWaitError(err error) {
var ej app.RevertCompoundError
if !errors.As(se.InnerErr, &ej) {
// does not require special handling
fmt.Print("fortify: " + e.Message())
fmsg.Print(e.Message())
} else {
errs := ej.Unwrap()
@ -35,10 +34,10 @@ func logWaitError(err error) {
var eb *fmsg.BaseError
if !errors.As(ei, &eb) {
// unreachable
fmt.Println("fortify: invalid error type returned by revert:", ei)
fmsg.Println("invalid error type returned by revert:", ei)
} else {
// print inner *app.BaseError message
fmt.Print("fortify: " + eb.Message())
fmsg.Print(eb.Message())
}
}
}
@ -50,13 +49,8 @@ func logBaseError(err error, message string) {
var e *fmsg.BaseError
if fmsg.AsBaseError(err, &e) {
fmt.Print("fortify: " + e.Message())
fmsg.Print(e.Message())
} else {
fmt.Println(message, err)
}
}
func fatalf(format string, a ...any) {
fmt.Printf("fortify: "+format, a...)
os.Exit(1)
}

View File

@ -4,7 +4,7 @@ import (
"os/exec"
"strings"
"git.ophivana.moe/security/fortify/internal/verbose"
"git.ophivana.moe/security/fortify/internal/fmsg"
)
func (a *app) commandBuilderMachineCtl(shimEnv string) (args []string) {
@ -14,7 +14,7 @@ func (a *app) commandBuilderMachineCtl(shimEnv string) (args []string) {
args = append(args, "shell", "--uid="+a.seal.sys.user.Username)
// --quiet
if !verbose.Get() {
if !fmsg.Verbose() {
args = append(args, "--quiet")
}

View File

@ -3,7 +3,7 @@ package app
import (
"os"
"git.ophivana.moe/security/fortify/internal/verbose"
"git.ophivana.moe/security/fortify/internal/fmsg"
)
const (
@ -18,7 +18,7 @@ func (a *app) commandBuilderSudo(shimEnv string) (args []string) {
// -A?
if _, ok := os.LookupEnv(sudoAskPass); ok {
verbose.Printf("%s set, adding askpass flag\n", sudoAskPass)
fmsg.VPrintln(sudoAskPass, "set, adding askpass flag")
args = append(args, "-A")
}

View File

@ -14,7 +14,6 @@ import (
"git.ophivana.moe/security/fortify/internal/shim"
"git.ophivana.moe/security/fortify/internal/state"
"git.ophivana.moe/security/fortify/internal/system"
"git.ophivana.moe/security/fortify/internal/verbose"
)
const (
@ -152,7 +151,7 @@ func (a *app) Seal(config *Config) error {
// map sandbox config to bwrap
if config.Confinement.Sandbox == nil {
verbose.Println("sandbox configuration not supplied, PROCEED WITH CAUTION")
fmsg.VPrintln("sandbox configuration not supplied, PROCEED WITH CAUTION")
// permissive defaults
conf := &SandboxConfig{
@ -242,7 +241,7 @@ func (a *app) Seal(config *Config) error {
}
// verbose log seal information
verbose.Println("created application seal as user",
fmsg.VPrintln("created application seal as user",
seal.sys.user.Username, "("+seal.sys.user.Uid+"),",
"method:", config.Method+",",
"launcher:", seal.toolPath+",",

View File

@ -7,7 +7,6 @@ import (
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"time"
@ -16,7 +15,6 @@ import (
"git.ophivana.moe/security/fortify/internal/shim"
"git.ophivana.moe/security/fortify/internal/state"
"git.ophivana.moe/security/fortify/internal/system"
"git.ophivana.moe/security/fortify/internal/verbose"
)
// Start starts the fortified child
@ -71,14 +69,14 @@ func (a *app) Start() error {
Bwrap: a.seal.sys.bwrap,
WL: a.seal.wl != nil,
Verbose: verbose.Get(),
Verbose: fmsg.Verbose(),
}, a.seal.wl); err != nil {
return fmsg.WrapErrorSuffix(err,
"cannot serve shim setup:")
}
// start shim
verbose.Println("starting shim as target user:", a.cmd)
fmsg.VPrintln("starting shim as target user:", a.cmd)
if err := a.cmd.Start(); err != nil {
return fmsg.WrapErrorSuffix(err,
"cannot start process:")
@ -178,12 +176,12 @@ func (a *app) Wait() (int, error) {
r = a.cmd.ProcessState.ExitCode()
}
verbose.Println("process", strconv.Itoa(a.cmd.Process.Pid), "exited with exit code", r)
fmsg.VPrintf("process %d exited with exit code %d", a.cmd.Process.Pid, r)
// close wayland connection
if a.seal.wl != nil {
if err := a.seal.wl.Close(); err != nil {
fmt.Println("fortify: cannot close wayland connection:", err)
fmsg.Println("cannot close wayland connection:", err)
}
}
@ -205,10 +203,10 @@ func (a *app) Wait() (int, error) {
} else {
if l := len(states); l == 0 {
// cleanup globals as the final launcher
verbose.Println("no other launchers active, will clean up globals")
fmsg.VPrintln("no other launchers active, will clean up globals")
ec.Set(system.User)
} else {
verbose.Printf("found %d active launchers, cleaning up without globals\n", l)
fmsg.VPrintf("found %d active launchers, cleaning up without globals", l)
}
// accumulate capabilities of other launchers
@ -222,7 +220,7 @@ func (a *app) Wait() (int, error) {
ec.Set(i)
}
}
if verbose.Get() {
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) {
@ -230,7 +228,7 @@ func (a *app) Wait() (int, error) {
}
}
if len(labels) > 0 {
verbose.Println("reverting operations labelled", strings.Join(labels, ", "))
fmsg.VPrintln("reverting operations labelled", strings.Join(labels, ", "))
}
}

View File

@ -2,9 +2,10 @@ package internal
import (
"errors"
"fmt"
"io/fs"
"os"
"git.ophivana.moe/security/fortify/internal/fmsg"
)
const (
@ -13,7 +14,7 @@ const (
var SdBootedV = func() bool {
if v, err := SdBooted(); err != nil {
fmt.Println("warn: read systemd marker:", err)
fmsg.Println("cannot read systemd marker:", err)
return false
} else {
return v

View File

@ -1,13 +1,12 @@
package internal
import (
"fmt"
"os"
"path"
"strconv"
"sync"
"git.ophivana.moe/security/fortify/internal/verbose"
"git.ophivana.moe/security/fortify/internal/fmsg"
)
// state that remain constant for the lifetime of the process
@ -37,16 +36,16 @@ func copySC() {
SharePath: path.Join(os.TempDir(), "fortify."+strconv.Itoa(os.Geteuid())),
}
verbose.Println("process share directory at", sc.SharePath)
fmsg.VPrintf("process share directory at %q", sc.SharePath)
// runtimePath, runDirPath
if r, ok := os.LookupEnv(xdgRuntimeDir); !ok {
fmt.Println("Env variable", xdgRuntimeDir, "unset")
fmsg.Println("variable", xdgRuntimeDir, "unset")
os.Exit(1)
} else {
sc.RuntimePath = r
sc.RunDirPath = path.Join(sc.RuntimePath, "fortify")
verbose.Println("XDG runtime directory at", sc.RunDirPath)
fmsg.VPrintf("XDG runtime directory at %q", sc.RunDirPath)
}
scVal = sc

View File

@ -1,2 +1,41 @@
// Package fmsg provides various functions for output messages.
package fmsg
import (
"log"
"os"
"sync/atomic"
)
var (
std = log.New(os.Stdout, "fortify: ", 0)
warn = log.New(os.Stderr, "fortify: ", 0)
verbose = new(atomic.Bool)
)
func SetPrefix(prefix string) {
prefix += ": "
std.SetPrefix(prefix)
warn.SetPrefix(prefix)
}
func Print(v ...any) {
warn.Print(v...)
}
func Printf(format string, v ...any) {
warn.Printf(format, v...)
}
func Println(v ...any) {
warn.Println(v...)
}
func Fatal(v ...any) {
warn.Fatal(v...)
}
func Fatalf(format string, v ...any) {
warn.Fatalf(format, v...)
}

21
internal/fmsg/verbose.go Normal file
View File

@ -0,0 +1,21 @@
package fmsg
func Verbose() bool {
return verbose.Load()
}
func SetVerbose(v bool) {
verbose.Store(v)
}
func VPrintf(format string, v ...any) {
if verbose.Load() {
std.Printf(format, v...)
}
}
func VPrintln(v ...any) {
if verbose.Load() {
std.Println(v...)
}
}

View File

@ -4,7 +4,6 @@ import (
"encoding/gob"
"errors"
"flag"
"fmt"
"os"
"os/exec"
"os/signal"
@ -13,7 +12,7 @@ import (
"syscall"
"time"
"git.ophivana.moe/security/fortify/internal/verbose"
"git.ophivana.moe/security/fortify/internal/fmsg"
)
const (
@ -25,49 +24,46 @@ const (
// proceed with caution!
func doInit(fd uintptr) {
fmsg.SetPrefix("init")
// re-exec
if len(os.Args) > 0 && os.Args[0] != "fortify" && path.IsAbs(os.Args[0]) {
if err := syscall.Exec(os.Args[0], []string{"fortify", "init"}, os.Environ()); err != nil {
fmt.Println("fortify-init: cannot re-exec self:", err)
fmsg.Println("cannot re-exec self:", err)
// continue anyway
}
}
verbose.Prefix = "fortify-init:"
var payload Payload
p := os.NewFile(fd, "config-stream")
if p == nil {
fmt.Println("fortify-init: invalid config descriptor")
os.Exit(1)
fmsg.Fatal("invalid config descriptor")
}
if err := gob.NewDecoder(p).Decode(&payload); err != nil {
fmt.Println("fortify-init: cannot decode init payload:", err)
os.Exit(1)
fmsg.Fatal("cannot decode init payload:", err)
} else {
// sharing stdout with parent
// USE WITH CAUTION
verbose.Set(payload.Verbose)
fmsg.SetVerbose(payload.Verbose)
// child does not need to see this
if err = os.Unsetenv(EnvInit); err != nil {
fmt.Println("fortify-init: cannot unset", EnvInit+":", err)
fmsg.Println("cannot unset", EnvInit+":", err)
// not fatal
} else {
verbose.Println("received configuration")
fmsg.VPrintln("received configuration")
}
}
// close config fd
if err := p.Close(); err != nil {
fmt.Println("fortify-init: cannot close config fd:", err)
fmsg.Println("cannot close config fd:", err)
// not fatal
}
// die with parent
if _, _, errno := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, uintptr(syscall.SIGKILL), 0); errno != 0 {
fmt.Println("fortify-init: prctl(PR_SET_PDEATHSIG, SIGKILL):", errno.Error())
os.Exit(1)
fmsg.Fatal("prctl(PR_SET_PDEATHSIG, SIGKILL):", errno.Error())
}
cmd := exec.Command(payload.Argv0)
@ -84,8 +80,7 @@ func doInit(fd uintptr) {
}
if err := cmd.Start(); err != nil {
fmt.Printf("fortify-init: cannot start %q: %v", payload.Argv0, err)
os.Exit(1)
fmsg.Fatalf("cannot start %q: %v", payload.Argv0, err)
}
sig := make(chan os.Signal, 2)
@ -121,7 +116,7 @@ func doInit(fd uintptr) {
}
}
if !errors.Is(err, syscall.ECHILD) {
fmt.Println("fortify-init: unexpected wait4 response:", err)
fmsg.Println("unexpected wait4 response:", err)
}
close(done)
@ -133,7 +128,7 @@ func doInit(fd uintptr) {
for {
select {
case s := <-sig:
verbose.Println("received", s.String())
fmsg.VPrintln("received", s.String())
os.Exit(0)
case w := <-info:
if w.wpid == cmd.Process.Pid {
@ -154,7 +149,7 @@ func doInit(fd uintptr) {
case <-done:
os.Exit(r)
case <-timeout:
fmt.Println("fortify-init: timeout exceeded waiting for lingering processes")
fmsg.Println("timeout exceeded waiting for lingering processes")
os.Exit(r)
}
}
@ -169,8 +164,7 @@ func Try() {
if args := flag.Args(); len(args) == 1 && args[0] == "init" {
if s, ok := os.LookupEnv(EnvInit); ok {
if fd, err := strconv.Atoi(s); err != nil {
fmt.Printf("fortify-init: cannot parse %q: %v", s, err)
os.Exit(1)
fmsg.Fatalf("cannot parse %q: %v", s, err)
} else {
doInit(uintptr(fd))
}

View File

@ -4,7 +4,6 @@ import (
"encoding/gob"
"errors"
"flag"
"fmt"
"net"
"os"
"path"
@ -12,29 +11,29 @@ import (
"syscall"
"git.ophivana.moe/security/fortify/helper"
"git.ophivana.moe/security/fortify/internal/fmsg"
init0 "git.ophivana.moe/security/fortify/internal/init"
"git.ophivana.moe/security/fortify/internal/verbose"
)
// everything beyond this point runs as target user
// proceed with caution!
func doShim(socket string) {
fmsg.SetPrefix("shim")
// re-exec
if len(os.Args) > 0 && os.Args[0] != "fortify" && path.IsAbs(os.Args[0]) {
if err := syscall.Exec(os.Args[0], []string{"fortify", "shim"}, os.Environ()); err != nil {
fmt.Println("fortify-shim: cannot re-exec self:", err)
fmsg.Println("cannot re-exec self:", err)
// continue anyway
}
}
verbose.Prefix = "fortify-shim:"
// dial setup socket
var conn *net.UnixConn
if c, err := net.DialUnix("unix", nil, &net.UnixAddr{Name: socket, Net: "unix"}); err != nil {
fmt.Println("fortify-shim: cannot dial setup socket:", err)
os.Exit(1)
fmsg.Fatal("cannot dial setup socket:", err)
panic("unreachable")
} else {
conn = c
}
@ -42,25 +41,22 @@ func doShim(socket string) {
// decode payload gob stream
var payload Payload
if err := gob.NewDecoder(conn).Decode(&payload); err != nil {
fmt.Println("fortify-shim: cannot decode shim payload:", err)
os.Exit(1)
fmsg.Fatal("cannot decode shim payload:", err)
} else {
// sharing stdout with parent
// USE WITH CAUTION
verbose.Set(payload.Verbose)
fmsg.SetVerbose(payload.Verbose)
}
if payload.Bwrap == nil {
fmt.Println("fortify-shim: bwrap config not supplied")
os.Exit(1)
fmsg.Fatal("bwrap config not supplied")
}
// receive wayland fd over socket
wfd := -1
if payload.WL {
if fd, err := receiveWLfd(conn); err != nil {
fmt.Println("fortify-shim: cannot receive wayland fd:", err)
os.Exit(1)
fmsg.Fatal("cannot receive wayland fd:", err)
} else {
wfd = fd
}
@ -68,7 +64,7 @@ func doShim(socket string) {
// close setup socket
if err := conn.Close(); err != nil {
fmt.Println("fortify-shim: cannot close setup socket:", err)
fmsg.Println("cannot close setup socket:", err)
// not fatal
}
@ -83,8 +79,7 @@ func doShim(socket string) {
// no argv, look up shell instead
var ok bool
if ic.Argv0, ok = os.LookupEnv("SHELL"); !ok {
fmt.Println("fortify-shim: no command was specified and $SHELL was unset")
os.Exit(1)
fmsg.Fatal("no command was specified and $SHELL was unset")
}
ic.Argv = []string{ic.Argv0}
@ -106,41 +101,37 @@ func doShim(socket string) {
// share config pipe
if r, w, err := os.Pipe(); err != nil {
fmt.Println("fortify-shim: cannot pipe:", err)
os.Exit(1)
fmsg.Fatal("cannot pipe:", err)
} else {
conf.SetEnv[init0.EnvInit] = strconv.Itoa(3 + len(extraFiles))
extraFiles = append(extraFiles, r)
verbose.Println("transmitting config to init")
fmsg.VPrintln("transmitting config to init")
go func() {
// stream config to pipe
if err = gob.NewEncoder(w).Encode(&ic); err != nil {
fmt.Println("fortify-shim: cannot transmit init config:", err)
os.Exit(1)
fmsg.Fatal("cannot transmit init config:", err)
}
}()
}
helper.BubblewrapName = payload.Exec[1] // resolved bwrap path by parent
if b, err := helper.NewBwrap(conf, nil, payload.Exec[0], func(int, int) []string { return []string{"init"} }); err != nil {
fmt.Println("fortify-shim: malformed sandbox config:", err)
os.Exit(1)
fmsg.Fatal("malformed sandbox config:", err)
} else {
cmd := b.Unwrap()
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
cmd.ExtraFiles = extraFiles
if verbose.Get() {
verbose.Println("bwrap args:", conf.Args())
if fmsg.Verbose() {
fmsg.VPrintln("bwrap args:", conf.Args())
}
// run and pass through exit code
if err = b.Start(); err != nil {
fmt.Println("fortify-shim: cannot start target process:", err)
os.Exit(1)
fmsg.Fatal("cannot start target process:", err)
} else if err = b.Wait(); err != nil {
verbose.Println("wait:", err)
fmsg.VPrintln("wait:", err)
}
if b.Unwrap().ProcessState != nil {
os.Exit(b.Unwrap().ProcessState.ExitCode())

View File

@ -3,13 +3,12 @@ package shim
import (
"encoding/gob"
"errors"
"fmt"
"net"
"os"
"syscall"
"git.ophivana.moe/security/fortify/acl"
"git.ophivana.moe/security/fortify/internal/verbose"
"git.ophivana.moe/security/fortify/internal/fmsg"
)
// called in the parent process
@ -19,7 +18,7 @@ func ServeConfig(socket string, uid int, payload *Payload, wl *Wayland) error {
if f, err := net.DialUnix("unix", nil, &net.UnixAddr{Name: wl.Path, Net: "unix"}); err != nil {
return err
} else {
verbose.Println("connected to wayland at", wl)
fmsg.VPrintf("connected to wayland at %q", wl.Path)
wl.UnixConn = f
}
}
@ -27,18 +26,18 @@ func ServeConfig(socket string, uid int, payload *Payload, wl *Wayland) error {
if c, err := net.ListenUnix("unix", &net.UnixAddr{Name: socket, Net: "unix"}); err != nil {
return err
} else {
verbose.Println("configuring shim on socket", socket)
fmsg.VPrintf("configuring shim on socket %q", socket)
if err = acl.UpdatePerm(socket, uid, acl.Read, acl.Write, acl.Execute); err != nil {
fmt.Println("fortify: cannot change permissions of shim setup socket:", err)
fmsg.Println("cannot change permissions of shim setup socket:", err)
}
go func() {
var conn *net.UnixConn
if conn, err = c.AcceptUnix(); err != nil {
fmt.Println("fortify: cannot accept connection from shim:", err)
fmsg.Println("cannot accept connection from shim:", err)
} else {
if err = gob.NewEncoder(conn).Encode(*payload); err != nil {
fmt.Println("fortify: cannot stream shim payload:", err)
fmsg.Println("cannot stream shim payload:", err)
_ = os.Remove(socket)
return
}
@ -47,23 +46,23 @@ func ServeConfig(socket string, uid int, payload *Payload, wl *Wayland) error {
// get raw connection
var rc syscall.RawConn
if rc, err = wl.SyscallConn(); err != nil {
fmt.Println("fortify: cannot obtain raw wayland connection:", err)
fmsg.Println("cannot obtain raw wayland connection:", err)
return
} else {
go func() {
// pass wayland socket fd
if err = rc.Control(func(fd uintptr) {
if _, _, err = conn.WriteMsgUnix(nil, syscall.UnixRights(int(fd)), nil); err != nil {
fmt.Println("fortify: cannot pass wayland connection to shim:", err)
fmsg.Println("cannot pass wayland connection to shim:", err)
return
}
_ = conn.Close()
// block until shim exits
<-wl.done
verbose.Println("releasing wayland connection")
fmsg.VPrintln("releasing wayland connection")
}); err != nil {
fmt.Println("fortify: cannot obtain wayland connection fd:", err)
fmsg.Println("cannot obtain wayland connection fd:", err)
}
}()
}
@ -72,10 +71,10 @@ func ServeConfig(socket string, uid int, payload *Payload, wl *Wayland) error {
}
}
if err = c.Close(); err != nil {
fmt.Println("fortify: cannot close shim socket:", err)
fmsg.Println("cannot close shim socket:", err)
}
if err = os.Remove(socket); err != nil && !errors.Is(err, os.ErrNotExist) {
fmt.Println("fortify: cannot remove dangling shim socket:", err)
fmsg.Println("cannot remove dangling shim socket:", err)
}
}()
return nil

View File

@ -10,8 +10,8 @@ import (
"text/tabwriter"
"time"
"git.ophivana.moe/security/fortify/internal/fmsg"
"git.ophivana.moe/security/fortify/internal/system"
"git.ophivana.moe/security/fortify/internal/verbose"
)
// MustPrintLauncherStateSimpleGlobal prints active launcher states of all simple stores
@ -21,19 +21,19 @@ func MustPrintLauncherStateSimpleGlobal(w **tabwriter.Writer, runDir string) {
// read runtime directory to get all UIDs
if dirs, err := os.ReadDir(path.Join(runDir, "state")); err != nil && !errors.Is(err, os.ErrNotExist) {
fmt.Println("cannot read runtime directory:", err)
fmsg.Println("cannot read runtime directory:", err)
os.Exit(1)
} else {
for _, e := range dirs {
// skip non-directories
if !e.IsDir() {
verbose.Println("skipped non-directory entry", e.Name())
fmsg.VPrintf("skipped non-directory entry %q", e.Name())
continue
}
// skip non-numerical names
if _, err = strconv.Atoi(e.Name()); err != nil {
verbose.Println("skipped non-uid entry", e.Name())
fmsg.VPrintf("skipped non-uid entry %q", e.Name())
continue
}
@ -45,7 +45,7 @@ func MustPrintLauncherStateSimpleGlobal(w **tabwriter.Writer, runDir string) {
// mustPrintLauncherState causes store activity so store needs to be closed
if err = s.Close(); err != nil {
fmt.Printf("warn: error closing store for user %s: %s\n", e.Name(), err)
fmsg.Printf("cannot close store for user %q: %s", e.Name(), err)
}
}
}
@ -67,7 +67,7 @@ func (s *simpleStore) mustPrintLauncherState(w **tabwriter.Writer, now time.Time
*w = tabwriter.NewWriter(os.Stdout, 0, 1, 4, ' ', 0)
// write header when initialising
if !verbose.Get() {
if !fmsg.Verbose() {
_, _ = fmt.Fprintln(*w, "\tUID\tPID\tUptime\tEnablements\tMethod\tCommand")
} else {
// argv is emitted in body when verbose
@ -96,7 +96,7 @@ func (s *simpleStore) mustPrintLauncherState(w **tabwriter.Writer, now time.Time
ets.WriteString("(No enablements)")
}
if !verbose.Get() {
if !fmsg.Verbose() {
_, _ = fmt.Fprintf(*w, "\t%s\t%d\t%s\t%s\t%s\t%s\n",
s.path[len(s.path)-1], state.PID, now.Sub(state.Time).Round(time.Second).String(), strings.TrimPrefix(ets.String(), ", "), state.Method,
state.Command)
@ -110,15 +110,15 @@ func (s *simpleStore) mustPrintLauncherState(w **tabwriter.Writer, now time.Time
return nil
}()
}); err != nil {
fmt.Printf("cannot perform action on store '%s': %s\n", path.Join(s.path...), err)
fmsg.Printf("cannot perform action on store %q: %s", path.Join(s.path...), err)
if !ok {
fmt.Println("warn: store faulted before printing")
fmsg.Println("store faulted before printing")
os.Exit(1)
}
}
if innerErr != nil {
fmt.Printf("cannot print launcher state for store '%s': %s\n", path.Join(s.path...), innerErr)
fmsg.Printf("cannot print launcher state for store %q: %s", path.Join(s.path...), innerErr)
os.Exit(1)
}
}

View File

@ -6,7 +6,6 @@ import (
"git.ophivana.moe/security/fortify/acl"
"git.ophivana.moe/security/fortify/internal/fmsg"
"git.ophivana.moe/security/fortify/internal/verbose"
)
// UpdatePerm appends an ephemeral acl update Op.
@ -33,18 +32,20 @@ func (a *ACL) Type() Enablement {
}
func (a *ACL) apply(sys *I) error {
verbose.Println("applying ACL", a, "uid:", sys.uid, "type:", TypeString(a.et), "path:", a.path)
fmsg.VPrintf("applying ACL %s uid: %d type: %s path: %q",
a, sys.uid, TypeString(a.et), a.path)
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) {
verbose.Println("stripping ACL", a, "uid:", sys.uid, "type:", TypeString(a.et), "path:", a.path)
fmsg.VPrintf("stripping ACL %s uid: %d type: %s path: %q",
a, sys.uid, TypeString(a.et), a.path)
return fmsg.WrapErrorSuffix(acl.UpdatePerm(a.path, sys.uid),
fmt.Sprintf("cannot strip ACL entry from %q:", a.path))
} else {
verbose.Println("skipping ACL", a, "uid:", sys.uid, "tag:", TypeString(a.et), "path:", a.path)
fmsg.VPrintln("skipping ACL", a, "uid:", sys.uid, "tag:", TypeString(a.et), "path:", a.path)
return nil
}
}

View File

@ -2,12 +2,10 @@ package system
import (
"errors"
"fmt"
"os"
"git.ophivana.moe/security/fortify/dbus"
"git.ophivana.moe/security/fortify/internal/fmsg"
"git.ophivana.moe/security/fortify/internal/verbose"
)
var (
@ -42,12 +40,12 @@ func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath st
d.proxy = dbus.New(sessionBus, systemBus)
defer func() {
if verbose.Get() && d.proxy.Sealed() {
verbose.Println("sealed session proxy", session.Args(sessionBus))
if fmsg.Verbose() && d.proxy.Sealed() {
fmsg.VPrintln("sealed session proxy", session.Args(sessionBus))
if system != nil {
verbose.Println("sealed system proxy", system.Args(systemBus))
fmsg.VPrintln("sealed system proxy", system.Args(systemBus))
}
verbose.Println("message bus proxy final args:", d.proxy)
fmsg.VPrintln("message bus proxy final args:", d.proxy)
}
}()
@ -73,9 +71,9 @@ func (d *DBus) Type() Enablement {
}
func (d *DBus) apply(_ *I) error {
verbose.Printf("session bus proxy on %q for upstream %q\n", 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 {
verbose.Printf("system bus proxy on %q for upstream %q\n", 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])
}
// ready channel passed to dbus package
@ -86,27 +84,27 @@ func (d *DBus) apply(_ *I) error {
return fmsg.WrapErrorSuffix(err,
"cannot start message bus proxy:")
}
verbose.Println("starting message bus proxy:", d.proxy)
if verbose.Get() { // save the extra bwrap arg build when verbose logging is off
verbose.Println("message bus proxy bwrap args:", d.proxy.Bwrap())
fmsg.VPrintln("starting message bus proxy:", d.proxy)
if fmsg.Verbose() { // save the extra bwrap arg build when verbose logging is off
fmsg.VPrintln("message bus proxy bwrap args:", d.proxy.Bwrap())
}
// background wait for proxy instance and notify completion
go func() {
if err := d.proxy.Wait(); err != nil {
fmt.Println("fortify: message bus proxy exited with error:", err)
fmsg.Println("message bus proxy exited with error:", err)
go func() { ready <- err }()
} else {
verbose.Println("message bus proxy exit")
fmsg.VPrintln("message bus proxy exit")
}
// ensure socket removal so ephemeral directory is empty at revert
if err := os.Remove(d.proxy.Session()[1]); err != nil && !errors.Is(err, os.ErrNotExist) {
fmt.Println("fortify: cannot remove dangling session bus socket:", err)
fmsg.Println("cannot remove dangling session bus socket:", err)
}
if d.system {
if err := os.Remove(d.proxy.System()[1]); err != nil && !errors.Is(err, os.ErrNotExist) {
fmt.Println("fortify: cannot remove dangling system bus socket:", err)
fmsg.Println("cannot remove dangling system bus socket:", err)
}
}
@ -120,14 +118,14 @@ func (d *DBus) apply(_ *I) error {
return fmsg.WrapErrorSuffix(err,
"message bus proxy fault after start:")
}
verbose.Println("message bus proxy ready")
fmsg.VPrintln("message bus proxy ready")
return nil
}
func (d *DBus) revert(_ *I, _ *Criteria) error {
// criteria ignored here since dbus is always process-scoped
verbose.Println("terminating message bus proxy")
fmsg.VPrintln("terminating message bus proxy")
if err := d.proxy.Close(); err != nil {
if errors.Is(err, os.ErrClosed) {

View File

@ -6,7 +6,6 @@ import (
"os"
"git.ophivana.moe/security/fortify/internal/fmsg"
"git.ophivana.moe/security/fortify/internal/verbose"
)
// Ensure the existence and mode of a directory.
@ -37,7 +36,7 @@ func (m *Mkdir) Type() Enablement {
}
func (m *Mkdir) apply(_ *I) error {
verbose.Println("ensuring directory", m)
fmsg.VPrintln("ensuring directory", m)
// create directory
err := os.Mkdir(m.path, m.perm)
@ -58,11 +57,11 @@ func (m *Mkdir) revert(_ *I, ec *Criteria) error {
}
if ec.hasType(m) {
verbose.Println("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 {
verbose.Println("skipping ephemeral directory", m)
fmsg.VPrintln("skipping ephemeral directory", m)
return nil
}
}

View File

@ -2,8 +2,9 @@ package system
import (
"errors"
"fmt"
"sync"
"git.ophivana.moe/security/fortify/internal/fmsg"
)
const (
@ -80,7 +81,7 @@ func (sys *I) Commit() error {
if sp != nil {
// rollback partial commit
if err := sp.Revert(&Criteria{nil}); err != nil {
fmt.Println("fortify: errors returned reverting partial commit:", err)
fmsg.Println("errors returned reverting partial commit:", err)
}
}
}()

View File

@ -9,7 +9,6 @@ import (
"git.ophivana.moe/security/fortify/acl"
"git.ophivana.moe/security/fortify/internal/fmsg"
"git.ophivana.moe/security/fortify/internal/verbose"
)
// CopyFile registers an Op that copies path dst from src.
@ -72,15 +71,15 @@ func (t *Tmpfile) Type() Enablement {
func (t *Tmpfile) apply(_ *I) error {
switch t.method {
case tmpfileCopy:
verbose.Printf("publishing tmpfile %s\n", t)
fmsg.VPrintln("publishing tmpfile", t)
return fmsg.WrapErrorSuffix(copyFile(t.dst, t.src),
fmt.Sprintf("cannot copy tmpfile %q:", t.dst))
case tmpfileLink:
verbose.Printf("linking tmpfile %s\n", t)
fmsg.VPrintln("linking tmpfile", t)
return fmsg.WrapErrorSuffix(os.Link(t.src, t.dst),
fmt.Sprintf("cannot link tmpfile %q:", t.dst))
case tmpfileWrite:
verbose.Printf("writing %s\n", t)
fmsg.VPrintln("writing", t)
return fmsg.WrapErrorSuffix(os.WriteFile(t.dst, []byte(t.src), 0600),
fmt.Sprintf("cannot write tmpfile %q:", t.dst))
default:
@ -90,11 +89,11 @@ func (t *Tmpfile) apply(_ *I) error {
func (t *Tmpfile) revert(_ *I, ec *Criteria) error {
if ec.hasType(t) {
verbose.Printf("removing tmpfile %q\n", 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 {
verbose.Printf("skipping tmpfile %q\n", t.dst)
fmsg.VPrintf("skipping tmpfile %q", t.dst)
return nil
}
}

View File

@ -4,7 +4,6 @@ import (
"fmt"
"git.ophivana.moe/security/fortify/internal/fmsg"
"git.ophivana.moe/security/fortify/internal/verbose"
"git.ophivana.moe/security/fortify/xcb"
)
@ -23,18 +22,18 @@ func (x XHost) Type() Enablement {
}
func (x XHost) apply(_ *I) error {
verbose.Printf("inserting entry %s to X11\n", 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) {
verbose.Printf("deleting entry %s from X11\n", 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 {
verbose.Printf("skipping entry %s in X11\n", x)
fmsg.VPrintf("skipping entry %s in X11", x)
return nil
}
}

View File

@ -1,19 +0,0 @@
package verbose
import (
"fmt"
)
var Prefix = "fortify:"
func Println(a ...any) {
if verbose.Load() {
fmt.Println(append([]any{Prefix}, a...)...)
}
}
func Printf(format string, a ...any) {
if verbose.Load() {
fmt.Printf(Prefix+" "+format, a...)
}
}

View File

@ -1,67 +0,0 @@
package verbose_test
import (
"os"
"os/exec"
"strconv"
"strings"
"testing"
"git.ophivana.moe/security/fortify/internal/verbose"
)
const (
testVerbose = "GO_TEST_VERBOSE"
wantStdout = "fortify: println\nfortify: printf"
)
func TestPrinter(t *testing.T) {
switch os.Getenv(testVerbose) {
case "0":
verbose.Set(false)
case "1":
verbose.Set(true)
default:
return
}
verbose.Println("println")
verbose.Printf("%s", "printf")
}
func TestPrintf_Println(t *testing.T) {
testPrintfPrintln(t, false)
testPrintfPrintln(t, true)
// make -cover happy
stdout := os.Stdout
t.Cleanup(func() {
os.Stdout = stdout
})
os.Stdout = nil
verbose.Set(true)
verbose.Printf("")
verbose.Println()
}
func testPrintfPrintln(t *testing.T, v bool) {
t.Run("start verbose printer with verbose "+strconv.FormatBool(v), func(t *testing.T) {
stdout, stderr := new(strings.Builder), new(strings.Builder)
stdout.Grow(len(wantStdout))
cmd := exec.Command(os.Args[0], "-test.run=TestPrinter")
cmd.Stdout, cmd.Stderr = stdout, stderr
if v {
cmd.Env = append(cmd.Env, testVerbose+"=1")
} else {
cmd.Env = append(cmd.Env, testVerbose+"=0")
}
if err := cmd.Run(); err != nil {
panic("cannot run printer process: " + err.Error() + " stderr: " + stderr.String())
}
if got := stdout.String(); strings.Contains(got, wantStdout) != v {
t.Errorf("Print: got %v; want %t",
got, v)
}
})
}

View File

@ -1,13 +0,0 @@
package verbose
import "sync/atomic"
var verbose = new(atomic.Bool)
func Get() bool {
return verbose.Load()
}
func Set(v bool) {
verbose.Store(v)
}

View File

@ -1,20 +0,0 @@
package verbose_test
import (
"testing"
"git.ophivana.moe/security/fortify/internal/verbose"
)
func TestGetSet(t *testing.T) {
verbose.Set(false)
if verbose.Get() {
t.Errorf("Get() = true, want false")
}
verbose.Set(true)
if !verbose.Get() {
t.Errorf("Get() = false, want true")
}
}

15
main.go
View File

@ -2,15 +2,14 @@ package main
import (
"flag"
"fmt"
"os"
"syscall"
"git.ophivana.moe/security/fortify/internal"
"git.ophivana.moe/security/fortify/internal/app"
"git.ophivana.moe/security/fortify/internal/fmsg"
init0 "git.ophivana.moe/security/fortify/internal/init"
"git.ophivana.moe/security/fortify/internal/shim"
"git.ophivana.moe/security/fortify/internal/verbose"
)
var (
@ -24,14 +23,14 @@ func init() {
func main() {
// linux/sched/coredump.h
if _, _, errno := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_DUMPABLE, 0, 0); errno != 0 {
fmt.Printf("fortify: cannot set SUID_DUMP_DISABLE: %s", errno.Error())
fmsg.Printf("fortify: cannot set SUID_DUMP_DISABLE: %s", errno.Error())
}
flag.Parse()
verbose.Set(flagVerbose)
fmsg.SetVerbose(flagVerbose)
if internal.SdBootedV {
verbose.Println("system booted with systemd as init system")
fmsg.VPrintln("system booted with systemd as init system")
}
// shim/init early exit
@ -40,7 +39,7 @@ func main() {
// root check
if os.Getuid() == 0 {
fmt.Println("fortify: this program must not run as root")
fmsg.Println("this program must not run as root")
os.Exit(1)
}
@ -56,7 +55,7 @@ func main() {
r := 1
a, err := app.New()
if err != nil {
fatalf("cannot create app: %s\n", err)
fmsg.Fatalf("cannot create app: %s\n", err)
} else if err = a.Seal(loadConfig()); err != nil {
logBaseError(err, "fortify: cannot seal app:")
} else if err = a.Start(); err != nil {
@ -68,7 +67,7 @@ func main() {
logWaitError(err)
}
if err = a.WaitErr(); err != nil {
fmt.Println("fortify: inner wait failed:", err)
fmsg.Println("inner wait failed:", err)
}
os.Exit(r)
}

View File

@ -7,6 +7,7 @@ import (
"text/tabwriter"
"git.ophivana.moe/security/fortify/internal"
"git.ophivana.moe/security/fortify/internal/fmsg"
"git.ophivana.moe/security/fortify/internal/state"
)
@ -25,7 +26,7 @@ func tryState() {
state.MustPrintLauncherStateSimpleGlobal(&w, internal.GetSC().RunDirPath)
if w != nil {
if err := w.Flush(); err != nil {
fmt.Println("warn: error formatting output:", err)
fmsg.Println("cannot format output:", err)
}
} else {
fmt.Println("No information available")