Compare commits

..

No commits in common. "83e72c2b59e0d9de56279186ab10161df493a4cc" and "90b86a5531c2683e8348e7766486e684efd92bb9" have entirely different histories.

50 changed files with 535 additions and 489 deletions

View File

@ -2,10 +2,10 @@ package main
import (
"encoding/json"
"log"
"os"
"git.gensokyo.uk/security/fortify/dbus"
"git.gensokyo.uk/security/fortify/internal/fmsg"
"git.gensokyo.uk/security/fortify/internal/system"
)
@ -63,18 +63,18 @@ func loadBundleInfo(name string, beforeFail func()) *bundleInfo {
bundle := new(bundleInfo)
if f, err := os.Open(name); err != nil {
beforeFail()
log.Fatalf("cannot open bundle: %v", err)
fmsg.Fatalf("cannot open bundle: %v", err)
} else if err = json.NewDecoder(f).Decode(&bundle); err != nil {
beforeFail()
log.Fatalf("cannot parse bundle metadata: %v", err)
fmsg.Fatalf("cannot parse bundle metadata: %v", err)
} else if err = f.Close(); err != nil {
log.Printf("cannot close bundle metadata: %v", err)
fmsg.Printf("cannot close bundle metadata: %v", err)
// not fatal
}
if bundle.ID == "" {
beforeFail()
log.Fatal("application identifier must not be empty")
fmsg.Fatal("application identifier must not be empty")
}
return bundle
@ -82,7 +82,7 @@ func loadBundleInfo(name string, beforeFail func()) *bundleInfo {
func formatHostname(name string) string {
if h, err := os.Hostname(); err != nil {
log.Printf("cannot get hostname: %v", err)
fmsg.Printf("cannot get hostname: %v", err)
return "fortify-" + name
} else {
return h + "-" + name

View File

@ -3,12 +3,10 @@ package main
import (
"encoding/json"
"flag"
"log"
"os"
"path"
"git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/internal"
"git.gensokyo.uk/security/fortify/internal/fmsg"
)
@ -27,12 +25,12 @@ func actionInstall(args []string) {
args = set.Args()
if len(args) != 1 {
log.Fatal("invalid argument")
fmsg.Fatal("invalid argument")
}
pkgPath := args[0]
if !path.IsAbs(pkgPath) {
if dir, err := os.Getwd(); err != nil {
log.Fatalf("cannot get current directory: %v", err)
fmsg.Fatalf("cannot get current directory: %v", err)
} else {
pkgPath = path.Join(dir, pkgPath)
}
@ -56,7 +54,7 @@ func actionInstall(args []string) {
var workDir string
if p, err := os.MkdirTemp("", "fpkg.*"); err != nil {
log.Fatalf("cannot create temporary directory: %v", err)
fmsg.Fatalf("cannot create temporary directory: %v", err)
} else {
workDir = p
}
@ -80,17 +78,19 @@ func actionInstall(args []string) {
if s, err := os.Stat(pathSet.metaPath); err != nil {
if !os.IsNotExist(err) {
cleanup()
log.Fatalf("cannot access %q: %v", pathSet.metaPath, err)
fmsg.Fatalf("cannot access %q: %v", pathSet.metaPath, err)
panic("unreachable")
}
// did not modify app, clean installation condition met later
} else if s.IsDir() {
cleanup()
log.Fatalf("metadata path %q is not a file", pathSet.metaPath)
fmsg.Fatalf("metadata path %q is not a file", pathSet.metaPath)
panic("unreachable")
} else {
app = loadBundleInfo(pathSet.metaPath, cleanup)
if app.ID != bundle.ID {
cleanup()
log.Fatalf("app %q claims to have identifier %q", bundle.ID, app.ID)
fmsg.Fatalf("app %q claims to have identifier %q", bundle.ID, app.ID)
}
// sec: should verify credentials
}
@ -102,20 +102,21 @@ func actionInstall(args []string) {
app.Launcher == bundle.Launcher &&
app.ActivationPackage == bundle.ActivationPackage {
cleanup()
log.Printf("package %q is identical to local application %q", pkgPath, app.ID)
internal.Exit(0)
fmsg.Printf("package %q is identical to local application %q", pkgPath, app.ID)
fmsg.Exit(0)
}
// AppID determines uid
if app.AppID != bundle.AppID {
cleanup()
log.Fatalf("package %q app id %d differs from installed %d", pkgPath, bundle.AppID, app.AppID)
fmsg.Fatalf("package %q app id %d differs from installed %d", pkgPath, bundle.AppID, app.AppID)
panic("unreachable")
}
// sec: should compare version string
fmsg.Verbosef("installing application %q version %q over local %q", bundle.ID, bundle.Version, app.Version)
fmsg.VPrintf("installing application %q version %q over local %q", bundle.ID, bundle.Version, app.Version)
} else {
fmsg.Verbosef("application %q clean installation", bundle.ID)
fmsg.VPrintf("application %q clean installation", bundle.ID)
// sec: should install credentials
}
@ -173,18 +174,21 @@ func actionInstall(args []string) {
// serialise metadata to ensure consistency
if f, err := os.OpenFile(pathSet.metaPath+"~", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil {
cleanup()
log.Fatalf("cannot create metadata file: %v", err)
fmsg.Fatalf("cannot create metadata file: %v", err)
panic("unreachable")
} else if err = json.NewEncoder(f).Encode(bundle); err != nil {
cleanup()
log.Fatalf("cannot write metadata: %v", err)
fmsg.Fatalf("cannot write metadata: %v", err)
panic("unreachable")
} else if err = f.Close(); err != nil {
log.Printf("cannot close metadata file: %v", err)
fmsg.Printf("cannot close metadata file: %v", err)
// not fatal
}
if err := os.Rename(pathSet.metaPath+"~", pathSet.metaPath); err != nil {
cleanup()
log.Fatalf("cannot rename metadata file: %v", err)
fmsg.Fatalf("cannot rename metadata file: %v", err)
panic("unreachable")
}
cleanup()

View File

@ -2,10 +2,8 @@ package main
import (
"flag"
"log"
"os"
"git.gensokyo.uk/security/fortify/internal"
"git.gensokyo.uk/security/fortify/internal/fmsg"
)
@ -13,7 +11,7 @@ const shell = "/run/current-system/sw/bin/bash"
func init() {
if err := os.Setenv("SHELL", shell); err != nil {
log.Fatalf("cannot set $SHELL: %v", err)
fmsg.Fatalf("cannot set $SHELL: %v", err)
}
}
@ -26,14 +24,14 @@ func init() {
}
func main() {
fmsg.Prepare("fpkg")
fmsg.SetPrefix("fpkg")
flag.Parse()
fmsg.Store(flagVerbose)
fmsg.SetVerbose(flagVerbose)
args := flag.Args()
if len(args) < 1 {
log.Fatal("invalid argument")
fmsg.Fatal("invalid argument")
}
switch args[0] {
@ -43,8 +41,8 @@ func main() {
actionStart(args[1:])
default:
log.Fatal("invalid argument")
fmsg.Fatal("invalid argument")
}
internal.Exit(0)
fmsg.Exit(0)
}

View File

@ -1,7 +1,6 @@
package main
import (
"log"
"os"
"os/exec"
"path"
@ -26,8 +25,8 @@ func init() {
func lookPath(file string) string {
if p, err := exec.LookPath(file); err != nil {
log.Fatalf("%s: command not found", file)
return ""
fmsg.Fatalf("%s: command not found", file)
panic("unreachable")
} else {
return p
}
@ -36,14 +35,15 @@ func lookPath(file string) string {
var beforeRunFail = new(atomic.Pointer[func()])
func mustRun(name string, arg ...string) {
fmsg.Verbosef("spawning process: %q %q", name, arg)
fmsg.VPrintf("spawning process: %q %q", name, arg)
cmd := exec.Command(name, arg...)
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
if err := cmd.Run(); err != nil {
if f := beforeRunFail.Swap(nil); f != nil {
(*f)()
}
log.Fatalf("%s: %v", name, err)
fmsg.Fatalf("%s: %v", name, err)
panic("unreachable")
}
}

View File

@ -4,7 +4,6 @@ import (
"encoding/json"
"errors"
"io"
"log"
"os"
"os/exec"
@ -26,12 +25,14 @@ func fortifyApp(config *fst.Config, beforeFail func()) {
)
if p, ok := internal.Path(Fmain); !ok {
beforeFail()
log.Fatal("invalid fortify path, this copy of fpkg is not compiled correctly")
fmsg.Fatal("invalid fortify path, this copy of fpkg is not compiled correctly")
panic("unreachable")
} else if r, w, err := os.Pipe(); err != nil {
beforeFail()
log.Fatalf("cannot pipe: %v", err)
fmsg.Fatalf("cannot pipe: %v", err)
panic("unreachable")
} else {
if fmsg.Load() {
if fmsg.Verbose() {
cmd = exec.Command(p, "-v", "app", "3")
} else {
cmd = exec.Command(p, "app", "3")
@ -44,22 +45,26 @@ func fortifyApp(config *fst.Config, beforeFail func()) {
go func() {
if err := json.NewEncoder(st).Encode(config); err != nil {
beforeFail()
log.Fatalf("cannot send configuration: %v", err)
fmsg.Fatalf("cannot send configuration: %v", err)
panic("unreachable")
}
}()
if err := cmd.Start(); err != nil {
beforeFail()
log.Fatalf("cannot start fortify: %v", err)
fmsg.Fatalf("cannot start fortify: %v", err)
panic("unreachable")
}
if err := cmd.Wait(); err != nil {
var exitError *exec.ExitError
if errors.As(err, &exitError) {
beforeFail()
internal.Exit(exitError.ExitCode())
fmsg.Exit(exitError.ExitCode())
panic("unreachable")
} else {
beforeFail()
log.Fatalf("cannot wait: %v", err)
fmsg.Fatalf("cannot wait: %v", err)
panic("unreachable")
}
}
}

View File

@ -2,12 +2,11 @@ package main
import (
"flag"
"log"
"path"
"git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/helper/bwrap"
"git.gensokyo.uk/security/fortify/internal"
"git.gensokyo.uk/security/fortify/internal/fmsg"
)
func actionStart(args []string) {
@ -27,7 +26,7 @@ func actionStart(args []string) {
args = set.Args()
if len(args) < 1 {
log.Fatal("invalid argument")
fmsg.Fatal("invalid argument")
}
/*
@ -38,7 +37,7 @@ func actionStart(args []string) {
pathSet := pathSetByApp(id)
app := loadBundleInfo(pathSet.metaPath, func() {})
if app.ID != id {
log.Fatalf("app %q claims to have identifier %q", id, app.ID)
fmsg.Fatalf("app %q claims to have identifier %q", id, app.ID)
}
/*
@ -145,7 +144,7 @@ func actionStart(args []string) {
*/
fortifyApp(config, func() {})
internal.Exit(0)
fmsg.Exit(0)
}
func appendGPUFilesystem(config *fst.Config) {

View File

@ -6,7 +6,7 @@ import (
"git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/helper/bwrap"
"git.gensokyo.uk/security/fortify/internal"
"git.gensokyo.uk/security/fortify/internal/fmsg"
)
func withNixDaemon(
@ -95,7 +95,7 @@ func fortifyAppDropShell(config *fst.Config, dropShell bool, beforeFail func())
config.Command = []string{shell, "-l"}
fortifyApp(config, beforeFail)
beforeFail()
internal.Exit(0)
fmsg.Exit(0)
}
fortifyApp(config, beforeFail)
}

View File

@ -2,7 +2,6 @@ package main
import (
"errors"
"log"
"git.gensokyo.uk/security/fortify/internal/app"
"git.gensokyo.uk/security/fortify/internal/fmsg"
@ -11,13 +10,13 @@ import (
func logWaitError(err error) {
var e *fmsg.BaseError
if !fmsg.AsBaseError(err, &e) {
log.Println("wait failed:", err)
fmsg.Println("wait failed:", err)
} else {
// Wait only returns either *app.ProcessError or *app.StateStoreError wrapped in a *app.BaseError
var se *app.StateStoreError
if !errors.As(err, &se) {
// does not need special handling
log.Print(e.Message())
fmsg.Print(e.Message())
} else {
// inner error are either unwrapped store errors
// or joined errors returned by *appSealTx revert
@ -25,7 +24,7 @@ func logWaitError(err error) {
var ej app.RevertCompoundError
if !errors.As(se.InnerErr, &ej) {
// does not require special handling
log.Print(e.Message())
fmsg.Print(e.Message())
} else {
errs := ej.Unwrap()
@ -34,10 +33,10 @@ func logWaitError(err error) {
var eb *fmsg.BaseError
if !errors.As(ei, &eb) {
// unreachable
log.Println("invalid error type returned by revert:", ei)
fmsg.Println("invalid error type returned by revert:", ei)
} else {
// print inner *app.BaseError message
log.Print(eb.Message())
fmsg.Print(eb.Message())
}
}
}
@ -49,8 +48,8 @@ func logBaseError(err error, message string) {
var e *fmsg.BaseError
if fmsg.AsBaseError(err, &e) {
log.Print(e.Message())
fmsg.Print(e.Message())
} else {
log.Println(message, err)
fmsg.Println(message, err)
}
}

View File

@ -88,10 +88,7 @@
touch $out
'';
nixos-tests = callPackage ./test.nix {
inherit system self home-manager;
inherit (self.packages.${system}) fortify;
};
nixos-tests = callPackage ./test.nix { inherit system self home-manager; };
}
);

View File

@ -53,7 +53,7 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
}
if s.Syscall == nil {
fmsg.Verbose("syscall filter not configured, PROCEED WITH CAUTION")
fmsg.VPrintln("syscall filter not configured, PROCEED WITH CAUTION")
}
var uid int
@ -121,11 +121,11 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
// get parent dir of socket
dir := path.Dir(pair[1])
if dir == "." || dir == "/" {
fmsg.Verbosef("dbus socket %q is in an unusual location", pair[1])
fmsg.VPrintf("dbus socket %q is in an unusual location", pair[1])
}
hidePaths = append(hidePaths, dir)
} else {
fmsg.Verbosef("dbus socket %q is not absolute", pair[1])
fmsg.VPrintf("dbus socket %q is not absolute", pair[1])
}
}
}
@ -169,7 +169,7 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
return nil, err
} else if ok {
hidePathMatch[i] = true
fmsg.Verbosef("hiding paths from %q", c.Src)
fmsg.VPrintf("hiding paths from %q", c.Src)
}
}
@ -221,7 +221,7 @@ func evalSymlinks(os linux.System, v *string) error {
if !errors.Is(err, fs.ErrNotExist) {
return err
}
fmsg.Verbosef("path %q does not yet exist", *v)
fmsg.VPrintf("path %q does not yet exist", *v)
} else {
*v = p
}

View File

@ -78,22 +78,11 @@ CopyBind(dest, payload, true) copy from FD to file which is bind-mounted on DEST
(--bind-data FD DEST)
*/
func (c *Config) CopyBind(dest string, payload []byte, opts ...bool) *Config {
var p *[]byte
c.CopyBindRef(dest, &p, opts...)
*p = payload
return c
}
// CopyBindRef is the same as CopyBind but writes the address of DataConfig.Data.
func (c *Config) CopyBindRef(dest string, payloadRef **[]byte, opts ...bool) *Config {
t := DataROBind
if len(opts) > 0 && opts[0] {
t = DataBind
}
d := &DataConfig{Dest: dest, Type: t}
*payloadRef = &d.Data
c.Filesystem = append(c.Filesystem, d)
c.Filesystem = append(c.Filesystem, &DataConfig{Dest: dest, Data: payload, Type: t})
return c
}

View File

@ -1,7 +1,6 @@
package bwrap_test
import (
"log"
"os"
"slices"
"testing"
@ -9,10 +8,11 @@ import (
"git.gensokyo.uk/security/fortify/helper/bwrap"
"git.gensokyo.uk/security/fortify/helper/proc"
"git.gensokyo.uk/security/fortify/helper/seccomp"
"git.gensokyo.uk/security/fortify/internal/fmsg"
)
func TestConfig_Args(t *testing.T) {
seccomp.CPrintln = log.Println
seccomp.CPrintln = fmsg.Println
t.Cleanup(func() { seccomp.CPrintln = nil })
testCases := []struct {

View File

@ -1,7 +1,6 @@
package internal
package proc
import (
"log"
"os"
"sync"
@ -15,8 +14,7 @@ var (
func copyExecutable() {
if name, err := os.Executable(); err != nil {
fmsg.BeforeExit()
log.Fatalf("cannot read executable path: %v", err)
fmsg.Fatalf("cannot read executable path: %v", err)
} else {
executable = name
}

View File

@ -4,12 +4,12 @@ import (
"crypto/sha512"
"errors"
"io"
"log"
"slices"
"syscall"
"testing"
"git.gensokyo.uk/security/fortify/helper/seccomp"
"git.gensokyo.uk/security/fortify/internal/fmsg"
)
func TestExport(t *testing.T) {
@ -79,7 +79,7 @@ func TestExport(t *testing.T) {
buf := make([]byte, 8)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
seccomp.CPrintln = log.Println
seccomp.CPrintln = fmsg.Println
t.Cleanup(func() { seccomp.CPrintln = nil })
e := seccomp.New(tc.opts)

View File

@ -14,7 +14,7 @@ import (
"git.gensokyo.uk/security/fortify/helper/bwrap"
"git.gensokyo.uk/security/fortify/helper/proc"
"git.gensokyo.uk/security/fortify/internal"
"git.gensokyo.uk/security/fortify/internal/fmsg"
)
// InternalChildStub is an internal function but exported because it is cross-package;
@ -40,7 +40,7 @@ func InternalChildStub() {
genericStub(flagRestoreFiles(4, ap, sp))
}
internal.Exit(0)
fmsg.Exit(0)
}
// InternalReplaceExecCommand is an internal function but exported because it is cross-package;

View File

@ -5,8 +5,8 @@ import (
"sync"
"git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/internal/app/shim"
"git.gensokyo.uk/security/fortify/internal/linux"
"git.gensokyo.uk/security/fortify/internal/priv/shim"
)
type App interface {

View File

@ -64,7 +64,7 @@ var testCasesNixos = []sealTestCase{
Ephemeral(system.Process, "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1", acl.Execute).
UpdatePermType(system.EWayland, "/run/user/1971/wayland-0", acl.Read, acl.Write, acl.Execute).
Link("/run/user/1971/pulse/native", "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse").
CopyFile(nil, "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
CopyFile("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie").
MustProxyDBus("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", &dbus.Config{
Talk: []string{
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
@ -213,7 +213,7 @@ var testCasesNixos = []sealTestCase{
CopyBind("/etc/group", []byte("fortify:x:1971:\n")).
Bind("/run/user/1971/wayland-0", "/run/user/1971/wayland-0").
Bind("/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse", "/run/user/1971/pulse/native").
CopyBind(fst.Tmp+"/pulse-cookie", nil).
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/pulse-cookie", fst.Tmp+"/pulse-cookie").
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", "/run/user/1971/bus").
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", "/run/dbus/system_bus_socket").
Tmpfs("/var/run/nscd", 8192).

View File

@ -219,7 +219,7 @@ var testCasesPd = []sealTestCase{
Ensure("/tmp/fortify.1971/wayland", 0711).
Wayland("/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/1971/wayland-0", "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c").
Link("/run/user/1971/pulse/native", "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse").
CopyFile(new([]byte), "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
CopyFile("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie").
MustProxyDBus("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", &dbus.Config{
Talk: []string{
"org.freedesktop.Notifications",
@ -382,7 +382,7 @@ var testCasesPd = []sealTestCase{
CopyBind("/etc/group", []byte("fortify:x:65534:\n")).
Bind("/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/65534/wayland-0").
Bind("/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse", "/run/user/65534/pulse/native").
CopyBind(fst.Tmp+"/pulse-cookie", nil).
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/pulse-cookie", fst.Tmp+"/pulse-cookie").
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", "/run/user/65534/bus").
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", "/run/dbus/system_bus_socket").
Tmpfs("/var/run/nscd", 8192).

View File

@ -191,7 +191,7 @@ func (a *app) Seal(config *fst.Config) error {
// map sandbox config to bwrap
if config.Confinement.Sandbox == nil {
fmsg.Verbose("sandbox configuration not supplied, PROCEED WITH CAUTION")
fmsg.VPrintln("sandbox configuration not supplied, PROCEED WITH CAUTION")
// permissive defaults
conf := &fst.SandboxConfig{
@ -264,7 +264,7 @@ func (a *app) Seal(config *fst.Config) error {
}
// verbose log seal information
fmsg.Verbosef("created application seal for uid %s (%s) groups: %v, command: %s",
fmsg.VPrintf("created application seal for uid %s (%s) groups: %v, command: %s",
seal.sys.user.us, seal.sys.user.username, config.Confinement.Groups, config.Command)
// seal app and release lock

View File

@ -143,7 +143,7 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error {
if seal.et.Has(system.EWayland) {
var socketPath string
if name, ok := os.LookupEnv(wl.WaylandDisplay); !ok {
fmsg.Verbose(wl.WaylandDisplay + " is not set, assuming " + wl.FallbackName)
fmsg.VPrintln(wl.WaylandDisplay + " is not set, assuming " + wl.FallbackName)
socketPath = path.Join(seal.RuntimePath, wl.FallbackName)
} else if !path.IsAbs(name) {
socketPath = path.Join(seal.RuntimePath, name)
@ -166,7 +166,7 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error {
seal.sys.Wayland(outerPath, socketPath, appID, seal.id)
seal.sys.bwrap.Bind(outerPath, innerPath)
} else { // bind mount wayland socket (insecure)
fmsg.Verbose("direct wayland access, PROCEED WITH CAUTION")
fmsg.VPrintln("direct wayland access, PROCEED WITH CAUTION")
seal.sys.bwrap.Bind(socketPath, innerPath)
// ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`)
@ -229,13 +229,13 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error {
// publish current user's pulse cookie for target user
if src, err := discoverPulseCookie(os); err != nil {
// not fatal
fmsg.Verbose(strings.TrimSpace(err.(*fmsg.BaseError).Message()))
fmsg.VPrintln(strings.TrimSpace(err.(*fmsg.BaseError).Message()))
} else {
dst := path.Join(seal.share, "pulse-cookie")
innerDst := fst.Tmp + "/pulse-cookie"
seal.sys.bwrap.SetEnv[pulseCookie] = innerDst
payload := new([]byte)
seal.sys.bwrap.CopyBindRef(innerDst, &payload)
seal.sys.CopyFile(payload, src, 256, 256)
seal.sys.CopyFile(dst, src)
seal.sys.bwrap.Bind(dst, innerDst)
}
}

View File

@ -4,15 +4,14 @@ import (
"context"
"errors"
"fmt"
"log"
"os/exec"
"path/filepath"
"strings"
"time"
"git.gensokyo.uk/security/fortify/helper"
"git.gensokyo.uk/security/fortify/internal/app/shim"
"git.gensokyo.uk/security/fortify/internal/fmsg"
"git.gensokyo.uk/security/fortify/internal/priv/shim"
"git.gensokyo.uk/security/fortify/internal/state"
"git.gensokyo.uk/security/fortify/internal/system"
)
@ -82,7 +81,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
Bwrap: a.seal.sys.bwrap,
Home: a.seal.sys.user.data,
Verbose: fmsg.Load(),
Verbose: fmsg.Verbose(),
}); err != nil {
return err
}
@ -120,8 +119,8 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
} else {
rs.ExitCode = a.shim.Unwrap().ProcessState.ExitCode()
}
if fmsg.Load() {
fmsg.Verbosef("process %d exited with exit code %d", a.shim.Unwrap().Process.Pid, rs.ExitCode)
if fmsg.Verbose() {
fmsg.VPrintf("process %d exited with exit code %d", a.shim.Unwrap().Process.Pid, rs.ExitCode)
}
// this is reached when a fault makes an already running shim impossible to continue execution
@ -129,11 +128,11 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
// the effects of this is similar to the alternative exit path and ensures shim death
case err := <-a.shim.WaitFallback():
rs.ExitCode = 255
log.Printf("cannot terminate shim on faulted setup: %v", err)
fmsg.Printf("cannot terminate shim on faulted setup: %v", err)
// alternative exit path relying on shim behaviour on monitor process exit
case <-ctx.Done():
fmsg.Verbose("alternative exit path selected")
fmsg.VPrintln("alternative exit path selected")
}
// child process exited, resume output
@ -164,10 +163,10 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
} else {
if l := len(states); l == 0 {
// cleanup globals as the final launcher
fmsg.Verbose("no other launchers active, will clean up globals")
fmsg.VPrintln("no other launchers active, will clean up globals")
ec.Set(system.User)
} else {
fmsg.Verbosef("found %d active launchers, cleaning up without globals", l)
fmsg.VPrintf("found %d active launchers, cleaning up without globals", l)
}
// accumulate capabilities of other launchers
@ -175,7 +174,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
if s.Config != nil {
*rt |= s.Config.Confinement.Enablements
} else {
log.Printf("state entry %d does not contain config", i)
fmsg.Printf("state entry %d does not contain config", i)
}
}
}
@ -185,7 +184,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
ec.Set(i)
}
}
if fmsg.Load() {
if fmsg.Verbose() {
labels := make([]string, 0, system.ELen+1)
for i := system.Enablement(0); i < system.Enablement(system.ELen+2); i++ {
if ec.Has(i) {
@ -193,7 +192,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
}
}
if len(labels) > 0 {
fmsg.Verbose("reverting operations labelled", strings.Join(labels, ", "))
fmsg.VPrintln("reverting operations labelled", strings.Join(labels, ", "))
}
}

View File

@ -1,9 +0,0 @@
package internal
import (
"os"
"git.gensokyo.uk/security/fortify/internal/fmsg"
)
func Exit(code int) { fmsg.BeforeExit(); os.Exit(code) }

98
internal/fmsg/defer.go Normal file
View File

@ -0,0 +1,98 @@
package fmsg
import (
"os"
"sync"
"sync/atomic"
)
var (
wstate atomic.Bool
dropped atomic.Uint64
withhold = make(chan struct{}, 1)
msgbuf = make(chan dOp, 64) // these ops are tiny so a large buffer is allocated for withholding output
dequeueOnce sync.Once
queueSync sync.WaitGroup
)
func dequeue() {
go func() {
for {
select {
case op := <-msgbuf:
op.Do()
queueSync.Done()
case <-withhold:
<-withhold
}
}
}()
}
// queue submits ops to msgbuf but drops messages
// when the buffer is full and dequeue is withholding
func queue(op dOp) {
queueSync.Add(1)
select {
case msgbuf <- op:
default:
// send the op anyway if not withholding
// as dequeue will get to it eventually
if !wstate.Load() {
msgbuf <- op
} else {
queueSync.Done()
// increment dropped message count
dropped.Add(1)
}
}
}
type dOp interface{ Do() }
func Exit(code int) {
Resume() // resume here to avoid deadlock
queueSync.Wait()
os.Exit(code)
}
func Suspend() {
dequeueOnce.Do(dequeue)
if wstate.CompareAndSwap(false, true) {
queueSync.Wait()
withhold <- struct{}{}
}
}
func Resume() {
dequeueOnce.Do(dequeue)
if wstate.CompareAndSwap(true, false) {
withhold <- struct{}{}
if d := dropped.Swap(0); d != 0 {
Printf("dropped %d messages during withhold", d)
}
}
}
type dPrint []any
func (v dPrint) Do() {
std.Print(v...)
}
type dPrintf struct {
format string
v []any
}
func (d *dPrintf) Do() {
std.Printf(d.format, d.v...)
}
type dPrintln []any
func (v dPrintln) Do() {
std.Println(v...)
}

View File

@ -2,85 +2,39 @@
package fmsg
import (
"bytes"
"io"
"log"
"os"
"sync"
"sync/atomic"
"syscall"
)
const (
bufSize = 4 * 1024
bufSizeMax = 16 * 1024 * 1024
)
var std = log.New(os.Stderr, "fortify: ", 0)
var o = &suspendable{w: os.Stderr}
// Prepare configures the system logger for [Suspend] and [Resume] to take effect.
func Prepare(prefix string) { log.SetPrefix(prefix + ": "); log.SetFlags(0); log.SetOutput(o) }
type suspendable struct {
w io.Writer
s atomic.Bool
buf bytes.Buffer
bufOnce sync.Once
bufMu sync.Mutex
dropped int
func SetPrefix(prefix string) {
prefix += ": "
std.SetPrefix(prefix)
std.SetPrefix(prefix)
}
func (s *suspendable) Write(p []byte) (n int, err error) {
if !s.s.Load() {
return s.w.Write(p)
}
s.bufOnce.Do(func() { s.prepareBuf() })
s.bufMu.Lock()
defer s.bufMu.Unlock()
if l := len(p); s.buf.Len()+l > bufSizeMax {
s.dropped += l
return 0, syscall.ENOMEM
}
return s.buf.Write(p)
func Print(v ...any) {
dequeueOnce.Do(dequeue)
queue(dPrint(v))
}
func (s *suspendable) prepareBuf() { s.buf.Grow(bufSize) }
func (s *suspendable) Suspend() bool { return o.s.CompareAndSwap(false, true) }
func (s *suspendable) Resume() (resumed bool, dropped uintptr, n int64, err error) {
if o.s.CompareAndSwap(true, false) {
o.bufMu.Lock()
defer o.bufMu.Unlock()
resumed = true
dropped = uintptr(o.dropped)
o.dropped = 0
n, err = io.Copy(s.w, &s.buf)
s.buf = bytes.Buffer{}
s.prepareBuf()
}
return
func Printf(format string, v ...any) {
dequeueOnce.Do(dequeue)
queue(&dPrintf{format, v})
}
func Suspend() bool { return o.Suspend() }
func Resume() bool {
resumed, dropped, _, err := o.Resume()
if err != nil {
// probably going to result in an error as well,
// so this call is as good as unreachable
log.Printf("cannot dump buffer on resume: %v", err)
}
if resumed && dropped > 0 {
log.Fatalf("dropped %d bytes while output is suspended", dropped)
}
return resumed
func Println(v ...any) {
dequeueOnce.Do(dequeue)
queue(dPrintln(v))
}
func BeforeExit() {
if Resume() {
log.Printf("beforeExit reached on suspended output")
}
func Fatal(v ...any) {
Print(v...)
Exit(1)
}
func Fatalf(format string, v ...any) {
Printf(format, v...)
Exit(1)
}

View File

@ -1,23 +1,25 @@
package fmsg
import (
"log"
"sync/atomic"
)
import "sync/atomic"
var verbose = new(atomic.Bool)
func Load() bool { return verbose.Load() }
func Store(v bool) { verbose.Store(v) }
func Verbose() bool {
return verbose.Load()
}
func Verbosef(format string, v ...any) {
func SetVerbose(v bool) {
verbose.Store(v)
}
func VPrintf(format string, v ...any) {
if verbose.Load() {
log.Printf(format, v...)
Printf(format, v...)
}
}
func Verbose(v ...any) {
func VPrintln(v ...any) {
if verbose.Load() {
log.Println(v...)
Println(v...)
}
}

View File

@ -54,7 +54,7 @@ type Paths struct {
func CopyPaths(os System, v *Paths) {
v.SharePath = path.Join(os.TempDir(), "fortify."+strconv.Itoa(os.Geteuid()))
fmsg.Verbosef("process share directory at %q", v.SharePath)
fmsg.VPrintf("process share directory at %q", v.SharePath)
if r, ok := os.LookupEnv(xdgRuntimeDir); !ok || r == "" || !path.IsAbs(r) {
// fall back to path in share since fortify has no hard XDG dependency
@ -65,5 +65,5 @@ func CopyPaths(os System, v *Paths) {
v.RunDirPath = path.Join(v.RuntimePath, "fortify")
}
fmsg.Verbosef("runtime directory at %q", v.RunDirPath)
fmsg.VPrintf("runtime directory at %q", v.RunDirPath)
}

View File

@ -3,7 +3,6 @@ package linux
import (
"errors"
"io/fs"
"log"
"os"
"os/exec"
"os/user"
@ -12,6 +11,7 @@ import (
"sync"
"syscall"
"git.gensokyo.uk/security/fortify/helper/proc"
"git.gensokyo.uk/security/fortify/internal"
"git.gensokyo.uk/security/fortify/internal/fmsg"
)
@ -33,13 +33,13 @@ func (s *Std) Geteuid() int { return os.Geteuid(
func (s *Std) LookupEnv(key string) (string, bool) { return os.LookupEnv(key) }
func (s *Std) TempDir() string { return os.TempDir() }
func (s *Std) LookPath(file string) (string, error) { return exec.LookPath(file) }
func (s *Std) MustExecutable() string { return internal.MustExecutable() }
func (s *Std) MustExecutable() string { return proc.MustExecutable() }
func (s *Std) LookupGroup(name string) (*user.Group, error) { return user.LookupGroup(name) }
func (s *Std) ReadDir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
func (s *Std) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) }
func (s *Std) Open(name string) (fs.File, error) { return os.Open(name) }
func (s *Std) EvalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) }
func (s *Std) Exit(code int) { internal.Exit(code) }
func (s *Std) Exit(code int) { fmsg.Exit(code) }
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
@ -74,10 +74,8 @@ func (s *Std) Uid(aid int) (int, error) {
u.uid = -1
if fsu, ok := internal.Check(internal.Fsu); !ok {
fmsg.BeforeExit()
log.Fatal("invalid fsu path, this copy of fortify is not compiled correctly")
// unreachable
return 0, syscall.EBADE
fmsg.Fatal("invalid fsu path, this copy of fortify is not compiled correctly")
panic("unreachable")
} else {
cmd := exec.Command(fsu)
cmd.Path = fsu

View File

@ -4,7 +4,7 @@ import (
"os"
"path"
"git.gensokyo.uk/security/fortify/internal"
"git.gensokyo.uk/security/fortify/internal/fmsg"
)
// used by the parent process
@ -13,6 +13,6 @@ import (
func TryArgv0() {
if len(os.Args) > 0 && path.Base(os.Args[0]) == "init" {
Main()
internal.Exit(0)
fmsg.Exit(0)
}
}

View File

@ -2,7 +2,6 @@ package init0
import (
"errors"
"log"
"os"
"os/exec"
"os/signal"
@ -25,15 +24,17 @@ const (
func Main() {
// sharing stdout with shim
// USE WITH CAUTION
fmsg.Prepare("init")
fmsg.SetPrefix("init")
// setting this prevents ptrace
if err := internal.PR_SET_DUMPABLE__SUID_DUMP_DISABLE(); err != nil {
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
fmsg.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
panic("unreachable")
}
if os.Getpid() != 1 {
log.Fatal("this process must run as pid 1")
fmsg.Fatal("this process must run as pid 1")
panic("unreachable")
}
// receive setup payload
@ -43,29 +44,30 @@ func Main() {
)
if f, err := proc.Receive(Env, &payload); err != nil {
if errors.Is(err, proc.ErrInvalid) {
log.Fatal("invalid config descriptor")
fmsg.Fatal("invalid config descriptor")
}
if errors.Is(err, proc.ErrNotSet) {
log.Fatal("FORTIFY_INIT not set")
fmsg.Fatal("FORTIFY_INIT not set")
}
log.Fatalf("cannot decode init setup payload: %v", err)
fmsg.Fatalf("cannot decode init setup payload: %v", err)
panic("unreachable")
} else {
fmsg.Store(payload.Verbose)
fmsg.SetVerbose(payload.Verbose)
closeSetup = f
// child does not need to see this
if err = os.Unsetenv(Env); err != nil {
log.Printf("cannot unset %s: %v", Env, err)
fmsg.Printf("cannot unset %s: %v", Env, err)
// not fatal
} else {
fmsg.Verbose("received configuration")
fmsg.VPrintln("received configuration")
}
}
// die with parent
if err := internal.PR_SET_PDEATHSIG__SIGKILL(); err != nil {
log.Fatalf("prctl(PR_SET_PDEATHSIG, SIGKILL): %v", err)
fmsg.Fatalf("prctl(PR_SET_PDEATHSIG, SIGKILL): %v", err)
}
cmd := exec.Command(payload.Argv0)
@ -74,13 +76,13 @@ func Main() {
cmd.Env = os.Environ()
if err := cmd.Start(); err != nil {
log.Fatalf("cannot start %q: %v", payload.Argv0, err)
fmsg.Fatalf("cannot start %q: %v", payload.Argv0, err)
}
fmsg.Suspend()
// close setup pipe as setup is now complete
if err := closeSetup(); err != nil {
log.Println("cannot close setup pipe:", err)
fmsg.Println("cannot close setup pipe:", err)
// not fatal
}
@ -117,7 +119,7 @@ func Main() {
}
}
if !errors.Is(err, syscall.ECHILD) {
log.Println("unexpected wait4 response:", err)
fmsg.Println("unexpected wait4 response:", err)
}
close(done)
@ -130,12 +132,9 @@ func Main() {
for {
select {
case s := <-sig:
if fmsg.Resume() {
fmsg.Verbosef("terminating on %s after process start", s.String())
} else {
fmsg.Verbosef("terminating on %s", s.String())
}
internal.Exit(0)
fmsg.VPrintln("received", s.String())
fmsg.Resume() // output could still be withheld at this point, so resume is called
fmsg.Exit(0)
case w := <-info:
if w.wpid == cmd.Process.Pid {
// initial process exited, output is most likely available again
@ -156,10 +155,10 @@ func Main() {
}()
}
case <-done:
internal.Exit(r)
fmsg.Exit(r)
case <-timeout:
log.Println("timeout exceeded waiting for lingering processes")
internal.Exit(r)
fmsg.Println("timeout exceeded waiting for lingering processes")
fmsg.Exit(r)
}
}
}

View File

@ -3,7 +3,6 @@ package shim
import (
"context"
"errors"
"log"
"os"
"os/exec"
"os/signal"
@ -16,8 +15,8 @@ import (
"git.gensokyo.uk/security/fortify/helper/proc"
"git.gensokyo.uk/security/fortify/helper/seccomp"
"git.gensokyo.uk/security/fortify/internal"
init0 "git.gensokyo.uk/security/fortify/internal/app/init"
"git.gensokyo.uk/security/fortify/internal/fmsg"
init0 "git.gensokyo.uk/security/fortify/internal/priv/init"
)
// everything beyond this point runs as unconstrained target user
@ -26,11 +25,12 @@ import (
func Main() {
// sharing stdout with fortify
// USE WITH CAUTION
fmsg.Prepare("shim")
fmsg.SetPrefix("shim")
// setting this prevents ptrace
if err := internal.PR_SET_DUMPABLE__SUID_DUMP_DISABLE(); err != nil {
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
fmsg.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
panic("unreachable")
}
// receive setup payload
@ -40,20 +40,21 @@ func Main() {
)
if f, err := proc.Receive(Env, &payload); err != nil {
if errors.Is(err, proc.ErrInvalid) {
log.Fatal("invalid config descriptor")
fmsg.Fatal("invalid config descriptor")
}
if errors.Is(err, proc.ErrNotSet) {
log.Fatal("FORTIFY_SHIM not set")
fmsg.Fatal("FORTIFY_SHIM not set")
}
log.Fatalf("cannot decode shim setup payload: %v", err)
fmsg.Fatalf("cannot decode shim setup payload: %v", err)
panic("unreachable")
} else {
fmsg.Store(payload.Verbose)
fmsg.SetVerbose(payload.Verbose)
closeSetup = f
}
if payload.Bwrap == nil {
log.Fatal("bwrap config not supplied")
fmsg.Fatal("bwrap config not supplied")
}
// restore bwrap sync fd
@ -64,7 +65,7 @@ func Main() {
// close setup socket
if err := closeSetup(); err != nil {
log.Println("cannot close setup pipe:", err)
fmsg.Println("cannot close setup pipe:", err)
// not fatal
}
@ -72,15 +73,15 @@ func Main() {
if s, err := os.Stat(payload.Home); err != nil {
if os.IsNotExist(err) {
if err = os.Mkdir(payload.Home, 0700); err != nil {
log.Fatalf("cannot create home directory: %v", err)
fmsg.Fatalf("cannot create home directory: %v", err)
}
} else {
log.Fatalf("cannot access home directory: %v", err)
fmsg.Fatalf("cannot access home directory: %v", err)
}
// home directory is created, proceed
} else if !s.IsDir() {
log.Fatalf("data path %q is not a directory", payload.Home)
fmsg.Fatalf("data path %q is not a directory", payload.Home)
}
var ic init0.Payload
@ -94,10 +95,10 @@ func Main() {
// no argv, look up shell instead
var ok bool
if payload.Bwrap.SetEnv == nil {
log.Fatal("no command was specified and environment is unset")
fmsg.Fatal("no command was specified and environment is unset")
}
if ic.Argv0, ok = payload.Bwrap.SetEnv["SHELL"]; !ok {
log.Fatal("no command was specified and $SHELL was unset")
fmsg.Fatal("no command was specified and $SHELL was unset")
}
ic.Argv = []string{ic.Argv0}
@ -109,20 +110,20 @@ func Main() {
// serve setup payload
if fd, encoder, err := proc.Setup(&extraFiles); err != nil {
log.Fatalf("cannot pipe: %v", err)
fmsg.Fatalf("cannot pipe: %v", err)
} else {
conf.SetEnv[init0.Env] = strconv.Itoa(fd)
go func() {
fmsg.Verbose("transmitting config to init")
fmsg.VPrintln("transmitting config to init")
if err = encoder.Encode(&ic); err != nil {
log.Fatalf("cannot transmit init config: %v", err)
fmsg.Fatalf("cannot transmit init config: %v", err)
}
}()
}
helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent
if fmsg.Load() {
seccomp.CPrintln = log.Println
if fmsg.Verbose() {
seccomp.CPrintln = fmsg.Println
}
if b, err := helper.NewBwrap(
conf, path.Join(fst.Tmp, "sbin/init"),
@ -130,7 +131,7 @@ func Main() {
extraFiles,
syncFd,
); err != nil {
log.Fatalf("malformed sandbox config: %v", err)
fmsg.Fatalf("malformed sandbox config: %v", err)
} else {
b.Stdin(os.Stdin).Stdout(os.Stdout).Stderr(os.Stderr)
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
@ -138,15 +139,15 @@ func Main() {
// run and pass through exit code
if err = b.Start(ctx, false); err != nil {
log.Fatalf("cannot start target process: %v", err)
fmsg.Fatalf("cannot start target process: %v", err)
} else if err = b.Wait(); err != nil {
var exitError *exec.ExitError
if !errors.As(err, &exitError) {
log.Printf("wait: %v", err)
internal.Exit(127)
fmsg.Println("wait:", err)
fmsg.Exit(127)
panic("unreachable")
}
internal.Exit(exitError.ExitCode())
fmsg.Exit(exitError.ExitCode())
panic("unreachable")
}
}

View File

@ -54,8 +54,8 @@ func (s *Shim) Start(
// prepare user switcher invocation
var fsu string
if p, ok := internal.Path(internal.Fsu); !ok {
return nil, fmsg.WrapError(errors.New("bad fsu path"),
"invalid fsu path, this copy of fortify is not compiled correctly")
fmsg.Fatal("invalid fsu path, this copy of fortify is not compiled correctly")
panic("unreachable")
} else {
fsu = p
}
@ -75,7 +75,7 @@ func (s *Shim) Start(
// format fsu supplementary groups
if len(supp) > 0 {
fmsg.Verbosef("attaching supplementary group ids %s", supp)
fmsg.VPrintf("attaching supplementary group ids %s", supp)
s.cmd.Env = append(s.cmd.Env, "FORTIFY_GROUPS="+strings.Join(supp, " "))
}
s.cmd.Stdin, s.cmd.Stdout, s.cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
@ -87,7 +87,7 @@ func (s *Shim) Start(
s.sync = &fd
}
fmsg.Verbose("starting shim via fsu:", s.cmd)
fmsg.VPrintln("starting shim via fsu:", s.cmd)
// withhold messages to stderr
fmsg.Suspend()
if err := s.cmd.Start(); err != nil {

View File

@ -85,17 +85,17 @@ func (s *multiStore) List() ([]int, error) {
for _, e := range entries {
// skip non-directories
if !e.IsDir() {
fmsg.Verbosef("skipped non-directory entry %q", e.Name())
fmsg.VPrintf("skipped non-directory entry %q", e.Name())
continue
}
// skip non-numerical names
if v, err := strconv.Atoi(e.Name()); err != nil {
fmsg.Verbosef("skipped non-aid entry %q", e.Name())
fmsg.VPrintf("skipped non-aid entry %q", e.Name())
continue
} else {
if v < 0 || v > 9999 {
fmsg.Verbosef("skipped out of bounds entry %q", e.Name())
fmsg.VPrintf("skipped out of bounds entry %q", e.Name())
continue
}

View File

@ -36,18 +36,18 @@ func (a *ACL) Type() Enablement {
}
func (a *ACL) apply(sys *I) error {
fmsg.Verbose("applying ACL", a)
fmsg.VPrintln("applying ACL", a)
return fmsg.WrapErrorSuffix(acl.UpdatePerm(a.path, sys.uid, a.perms...),
fmt.Sprintf("cannot apply ACL entry to %q:", a.path))
}
func (a *ACL) revert(sys *I, ec *Criteria) error {
if ec.hasType(a) {
fmsg.Verbose("stripping ACL", a)
fmsg.VPrintln("stripping ACL", a)
return fmsg.WrapErrorSuffix(acl.UpdatePerm(a.path, sys.uid),
fmt.Sprintf("cannot strip ACL entry from %q:", a.path))
} else {
fmsg.Verbose("skipping ACL", a)
fmsg.VPrintln("skipping ACL", a)
return nil
}
}

View File

@ -3,7 +3,6 @@ package system
import (
"bytes"
"errors"
"log"
"strings"
"sync"
@ -48,12 +47,12 @@ func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath st
d.proxy = dbus.New(sessionBus, systemBus)
defer func() {
if fmsg.Load() && d.proxy.Sealed() {
fmsg.Verbose("sealed session proxy", session.Args(sessionBus))
if fmsg.Verbose() && d.proxy.Sealed() {
fmsg.VPrintln("sealed session proxy", session.Args(sessionBus))
if system != nil {
fmsg.Verbose("sealed system proxy", system.Args(systemBus))
fmsg.VPrintln("sealed system proxy", system.Args(systemBus))
}
fmsg.Verbose("message bus proxy final args:", d.proxy)
fmsg.VPrintln("message bus proxy final args:", d.proxy)
}
}()
@ -79,9 +78,9 @@ func (d *DBus) Type() Enablement {
}
func (d *DBus) apply(sys *I) error {
fmsg.Verbosef("session bus proxy on %q for upstream %q", d.proxy.Session()[1], d.proxy.Session()[0])
fmsg.VPrintf("session bus proxy on %q for upstream %q", d.proxy.Session()[1], d.proxy.Session()[0])
if d.system {
fmsg.Verbosef("system bus proxy on %q for upstream %q", d.proxy.System()[1], d.proxy.System()[0])
fmsg.VPrintf("system bus proxy on %q for upstream %q", d.proxy.System()[1], d.proxy.System()[0])
}
// this starts the process and blocks until ready
@ -90,15 +89,15 @@ func (d *DBus) apply(sys *I) error {
return fmsg.WrapErrorSuffix(err,
"cannot start message bus proxy:")
}
fmsg.Verbose("starting message bus proxy:", d.proxy)
fmsg.VPrintln("starting message bus proxy:", d.proxy)
return nil
}
func (d *DBus) revert(_ *I, _ *Criteria) error {
// criteria ignored here since dbus is always process-scoped
fmsg.Verbose("terminating message bus proxy")
fmsg.VPrintln("terminating message bus proxy")
d.proxy.Close()
defer fmsg.Verbose("message bus proxy exit")
defer fmsg.VPrintln("message bus proxy exit")
return fmsg.WrapErrorSuffix(d.proxy.Wait(), "message bus proxy error:")
}
@ -145,7 +144,7 @@ func (s *scanToFmsg) write(p []byte, a int) (int, error) {
func (s *scanToFmsg) Dump() {
s.mu.RLock()
for _, msg := range s.msgbuf {
log.Println(msg)
fmsg.Println(msg)
}
s.mu.RUnlock()
}

View File

@ -1,53 +0,0 @@
package system
import (
"fmt"
"os"
"git.gensokyo.uk/security/fortify/internal/fmsg"
)
// Link registers an Op that links dst to src.
func (sys *I) Link(oldname, newname string) *I { return sys.LinkFileType(Process, oldname, newname) }
// LinkFileType registers a file linking Op labelled with type et.
func (sys *I) LinkFileType(et Enablement, oldname, newname string) *I {
sys.lock.Lock()
defer sys.lock.Unlock()
sys.ops = append(sys.ops, &Hardlink{et, newname, oldname})
return sys
}
type Hardlink struct {
et Enablement
dst, src string
}
func (l *Hardlink) Type() Enablement { return l.et }
func (l *Hardlink) apply(_ *I) error {
fmsg.Verbose("linking", l)
return fmsg.WrapErrorSuffix(os.Link(l.src, l.dst),
fmt.Sprintf("cannot link %q:", l.dst))
}
func (l *Hardlink) revert(_ *I, ec *Criteria) error {
if ec.hasType(l) {
fmsg.Verbosef("removing hard link %q", l.dst)
return fmsg.WrapErrorSuffix(os.Remove(l.dst),
fmt.Sprintf("cannot remove hard link %q:", l.dst))
} else {
fmsg.Verbosef("skipping hard link %q", l.dst)
return nil
}
}
func (l *Hardlink) Is(o Op) bool {
l0, ok := o.(*Hardlink)
return ok && l0 != nil && *l == *l0
}
func (l *Hardlink) Path() string { return l.src }
func (l *Hardlink) String() string { return fmt.Sprintf("%q from %q", l.dst, l.src) }

View File

@ -40,7 +40,7 @@ func (m *Mkdir) Type() Enablement {
}
func (m *Mkdir) apply(_ *I) error {
fmsg.Verbose("ensuring directory", m)
fmsg.VPrintln("ensuring directory", m)
// create directory
err := os.Mkdir(m.path, m.perm)
@ -61,11 +61,11 @@ func (m *Mkdir) revert(_ *I, ec *Criteria) error {
}
if ec.hasType(m) {
fmsg.Verbose("destroying ephemeral directory", m)
fmsg.VPrintln("destroying ephemeral directory", m)
return fmsg.WrapErrorSuffix(os.Remove(m.path),
fmt.Sprintf("cannot remove ephemeral directory %q:", m.path))
} else {
fmsg.Verbose("skipping ephemeral directory", m)
fmsg.VPrintln("skipping ephemeral directory", m)
return nil
}
}

View File

@ -3,7 +3,6 @@ package system
import (
"context"
"errors"
"log"
"os"
"sync"
@ -106,9 +105,9 @@ func (sys *I) Commit(ctx context.Context) error {
// sp is set to nil when all ops are applied
if sp != nil {
// rollback partial commit
fmsg.Verbosef("commit faulted after %d ops, rolling back partial commit", len(sp.ops))
fmsg.VPrintf("commit faulted after %d ops, rolling back partial commit", len(sp.ops))
if err := sp.Revert(&Criteria{nil}); err != nil {
log.Println("errors returned reverting partial commit:", err)
fmsg.Println("errors returned reverting partial commit:", err)
}
}
}()

View File

@ -100,7 +100,7 @@ func TestI_Equal(t *testing.T) {
"op type mismatch",
system.New(150).
ChangeHosts("chronos").
CopyFile(new([]byte), "/home/ophestra/xdg/config/pulse/cookie", 0, 256),
CopyFile("/tmp/fortify.1971/30c9543e0a2c9621a8bfecb9d874c347/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie"),
system.New(150).
ChangeHosts("chronos").
Ensure("/run", 0755),

View File

@ -1,72 +1,117 @@
package system
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"syscall"
"strconv"
"git.gensokyo.uk/security/fortify/acl"
"git.gensokyo.uk/security/fortify/internal/fmsg"
)
// CopyFile registers an Op that copies from src.
// A buffer is initialised with size cap and the Op faults if bytes read exceed n.
func (sys *I) CopyFile(payload *[]byte, src string, cap int, n int64) *I {
buf := new(bytes.Buffer)
buf.Grow(cap)
// CopyFile registers an Op that copies path dst from src.
func (sys *I) CopyFile(dst, src string) *I {
return sys.CopyFileType(Process, dst, src)
}
// CopyFileType registers a file copying Op labelled with type et.
func (sys *I) CopyFileType(et Enablement, dst, src string) *I {
sys.lock.Lock()
sys.ops = append(sys.ops, &Tmpfile{payload, src, n, buf})
sys.ops = append(sys.ops, &Tmpfile{et, tmpfileCopy, dst, src})
sys.lock.Unlock()
sys.UpdatePermType(et, dst, acl.Read)
return sys
}
// Link registers an Op that links dst to src.
func (sys *I) Link(oldname, newname string) *I {
return sys.LinkFileType(Process, oldname, newname)
}
// LinkFileType registers a file linking Op labelled with type et.
func (sys *I) LinkFileType(et Enablement, oldname, newname string) *I {
sys.lock.Lock()
defer sys.lock.Unlock()
sys.ops = append(sys.ops, &Tmpfile{et, tmpfileLink, newname, oldname})
return sys
}
const (
tmpfileCopy uint8 = iota
tmpfileLink
)
type Tmpfile struct {
payload *[]byte
src string
n int64
buf *bytes.Buffer
et Enablement
method uint8
dst, src string
}
func (t *Tmpfile) Type() Enablement {
return t.et
}
func (t *Tmpfile) Type() Enablement { return Process }
func (t *Tmpfile) apply(_ *I) error {
fmsg.Verbose("copying", t)
if b, err := os.Stat(t.src); err != nil {
return fmsg.WrapErrorSuffix(err,
fmt.Sprintf("cannot stat %q:", t.src))
} else {
if b.IsDir() {
return fmsg.WrapErrorSuffix(syscall.EISDIR,
fmt.Sprintf("%q is a directory", t.src))
switch t.method {
case tmpfileCopy:
fmsg.VPrintln("publishing tmpfile", t)
return fmsg.WrapErrorSuffix(copyFile(t.dst, t.src),
fmt.Sprintf("cannot copy tmpfile %q:", t.dst))
case tmpfileLink:
fmsg.VPrintln("linking tmpfile", t)
return fmsg.WrapErrorSuffix(os.Link(t.src, t.dst),
fmt.Sprintf("cannot link tmpfile %q:", t.dst))
default:
panic("invalid tmpfile method " + strconv.Itoa(int(t.method)))
}
}
func (t *Tmpfile) revert(_ *I, ec *Criteria) error {
if ec.hasType(t) {
fmsg.VPrintf("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)
return nil
}
if s := b.Size(); s > t.n {
return fmsg.WrapErrorSuffix(syscall.ENOMEM,
fmt.Sprintf("file %q is too long: %d > %d",
t.src, s, t.n))
}
}
if f, err := os.Open(t.src); err != nil {
return fmsg.WrapErrorSuffix(err,
fmt.Sprintf("cannot open %q:", t.src))
} else if _, err = io.CopyN(t.buf, f, t.n); err != nil {
return fmsg.WrapErrorSuffix(err,
fmt.Sprintf("cannot read from %q:", t.src))
}
*t.payload = t.buf.Bytes()
return nil
}
func (t *Tmpfile) revert(*I, *Criteria) error { t.buf.Reset(); return nil }
func (t *Tmpfile) Is(o Op) bool {
t0, ok := o.(*Tmpfile)
return ok && t0 != nil &&
t.src == t0.src && t.n == t0.n
return ok && t0 != nil && *t == *t0
}
func (t *Tmpfile) Path() string { return t.src }
func (t *Tmpfile) String() string { return fmt.Sprintf("up to %d bytes from %q", t.n, t.src) }
func (t *Tmpfile) String() string {
switch t.method {
case tmpfileCopy:
return fmt.Sprintf("%q from %q", t.dst, t.src)
case tmpfileLink:
return fmt.Sprintf("%q from %q", t.dst, t.src)
default:
panic("invalid tmpfile method " + strconv.Itoa(int(t.method)))
}
}
func copyFile(dst, src string) error {
dstD, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
srcD, err := os.Open(src)
if err != nil {
return errors.Join(err, dstD.Close())
}
_, err = io.Copy(dstD, srcD)
return errors.Join(err, dstD.Close(), srcD.Close())
}

View File

@ -1,29 +1,50 @@
package system
import (
"strconv"
"testing"
"git.gensokyo.uk/security/fortify/acl"
)
func TestCopyFile(t *testing.T) {
testCases := []struct {
tcOp
cap int
n int64
dst, src string
}{
{tcOp{Process, "/home/ophestra/xdg/config/pulse/cookie"}, 256, 256},
{"/tmp/fortify.1971/f587afe9fce3c8e1ad5b64deb6c41ad5/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie"},
{"/tmp/fortify.1971/62154f708b5184ab01f9dcc2bbe7a33b/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie"},
}
for _, tc := range testCases {
t.Run("copy file "+tc.path+" with cap = "+strconv.Itoa(tc.cap)+" n = "+strconv.Itoa(int(tc.n)), func(t *testing.T) {
t.Run("copy file "+tc.dst+" from "+tc.src, func(t *testing.T) {
sys := New(150)
sys.CopyFile(new([]byte), tc.path, tc.cap, tc.n)
tc.test(t, sys.ops, []Op{
&Tmpfile{nil, tc.path, tc.n, nil},
sys.CopyFile(tc.dst, tc.src)
(&tcOp{Process, tc.src}).test(t, sys.ops, []Op{
&Tmpfile{Process, tmpfileCopy, tc.dst, tc.src},
&ACL{Process, tc.dst, []acl.Perm{acl.Read}},
}, "CopyFile")
})
}
}
func TestCopyFileType(t *testing.T) {
testCases := []struct {
tcOp
dst string
}{
{tcOp{User, "/tmp/fortify.1971/f587afe9fce3c8e1ad5b64deb6c41ad5/pulse-cookie"}, "/home/ophestra/xdg/config/pulse/cookie"},
{tcOp{Process, "/tmp/fortify.1971/62154f708b5184ab01f9dcc2bbe7a33b/pulse-cookie"}, "/home/ophestra/xdg/config/pulse/cookie"},
}
for _, tc := range testCases {
t.Run("copy file "+tc.dst+" from "+tc.path+" with type "+TypeString(tc.et), func(t *testing.T) {
sys := New(150)
sys.CopyFileType(tc.et, tc.dst, tc.path)
tc.test(t, sys.ops, []Op{
&Tmpfile{tc.et, tmpfileCopy, tc.dst, tc.path},
&ACL{tc.et, tc.dst, []acl.Perm{acl.Read}},
}, "CopyFileType")
})
}
}
func TestLink(t *testing.T) {
testCases := []struct {
dst, src string
@ -36,7 +57,7 @@ func TestLink(t *testing.T) {
sys := New(150)
sys.Link(tc.src, tc.dst)
(&tcOp{Process, tc.src}).test(t, sys.ops, []Op{
&Hardlink{Process, tc.dst, tc.src},
&Tmpfile{Process, tmpfileLink, tc.dst, tc.src},
}, "Link")
})
}
@ -55,25 +76,44 @@ func TestLinkFileType(t *testing.T) {
sys := New(150)
sys.LinkFileType(tc.et, tc.path, tc.dst)
tc.test(t, sys.ops, []Op{
&Hardlink{tc.et, tc.dst, tc.path},
&Tmpfile{tc.et, tmpfileLink, tc.dst, tc.path},
}, "LinkFileType")
})
}
}
func TestTmpfile_String(t *testing.T) {
t.Run("invalid method panic", func(t *testing.T) {
defer func() {
wantPanic := "invalid tmpfile method 255"
if r := recover(); r != wantPanic {
t.Errorf("String() panic = %v, want %v",
r, wantPanic)
}
}()
_ = (&Tmpfile{method: 255}).String()
})
testCases := []struct {
src string
n int64
method uint8
dst, src string
want string
}{
{"/home/ophestra/xdg/config/pulse/cookie", 256,
`up to 256 bytes from "/home/ophestra/xdg/config/pulse/cookie"`},
{tmpfileCopy, "/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie",
`"/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse-cookie" from "/home/ophestra/xdg/config/pulse/cookie"`},
{tmpfileLink, "/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/wayland", "/run/user/1971/wayland-0",
`"/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/wayland" from "/run/user/1971/wayland-0"`},
{tmpfileLink, "/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse", "/run/user/1971/pulse/native",
`"/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse" from "/run/user/1971/pulse/native"`},
}
for _, tc := range testCases {
t.Run(tc.want, func(t *testing.T) {
if got := (&Tmpfile{src: tc.src, n: tc.n}).String(); got != tc.want {
if got := (&Tmpfile{
method: tc.method,
dst: tc.dst,
src: tc.src,
}).String(); got != tc.want {
t.Errorf("String() = %v, want %v", got, tc.want)
}
})

View File

@ -45,7 +45,7 @@ func (w Wayland) apply(sys *I) error {
return fmsg.WrapErrorSuffix(err,
fmt.Sprintf("cannot attach to wayland on %q:", w.pair[1]))
} else {
fmsg.Verbosef("wayland attached on %q", w.pair[1])
fmsg.VPrintf("wayland attached on %q", w.pair[1])
}
if sp, err := w.conn.Bind(w.pair[0], w.appID, w.instanceID); err != nil {
@ -53,7 +53,7 @@ func (w Wayland) apply(sys *I) error {
fmt.Sprintf("cannot bind to socket on %q:", w.pair[0]))
} else {
sys.sp = sp
fmsg.Verbosef("wayland listening on %q", w.pair[0])
fmsg.VPrintf("wayland listening on %q", w.pair[0])
return fmsg.WrapErrorSuffix(errors.Join(os.Chmod(w.pair[0], 0), acl.UpdatePerm(w.pair[0], sys.uid, acl.Read, acl.Write, acl.Execute)),
fmt.Sprintf("cannot chmod socket on %q:", w.pair[0]))
}
@ -61,16 +61,16 @@ func (w Wayland) apply(sys *I) error {
func (w Wayland) revert(_ *I, ec *Criteria) error {
if ec.hasType(w) {
fmsg.Verbosef("removing wayland socket on %q", w.pair[0])
fmsg.VPrintf("removing wayland socket on %q", w.pair[0])
if err := os.Remove(w.pair[0]); err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}
fmsg.Verbosef("detaching from wayland on %q", w.pair[1])
fmsg.VPrintf("detaching from wayland on %q", w.pair[1])
return fmsg.WrapErrorSuffix(w.conn.Close(),
fmt.Sprintf("cannot detach from wayland on %q:", w.pair[1]))
} else {
fmsg.Verbosef("skipping wayland cleanup on %q", w.pair[0])
fmsg.VPrintf("skipping wayland cleanup on %q", w.pair[0])
return nil
}
}

View File

@ -24,18 +24,18 @@ func (x XHost) Type() Enablement {
}
func (x XHost) apply(_ *I) error {
fmsg.Verbosef("inserting entry %s to X11", x)
fmsg.VPrintf("inserting entry %s to X11", x)
return fmsg.WrapErrorSuffix(xcb.ChangeHosts(xcb.HostModeInsert, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)),
fmt.Sprintf("cannot insert entry %s to X11:", x))
}
func (x XHost) revert(_ *I, ec *Criteria) error {
if ec.hasType(x) {
fmsg.Verbosef("deleting entry %s from X11", x)
fmsg.VPrintf("deleting entry %s from X11", x)
return fmsg.WrapErrorSuffix(xcb.ChangeHosts(xcb.HostModeDelete, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)),
fmt.Sprintf("cannot delete entry %s from X11:", x))
} else {
fmsg.Verbosef("skipping entry %s in X11", x)
fmsg.VPrintf("skipping entry %s in X11", x)
return nil
}
}

71
main.go
View File

@ -5,7 +5,6 @@ import (
_ "embed"
"flag"
"fmt"
"log"
"os"
"os/signal"
"os/user"
@ -21,10 +20,10 @@ import (
"git.gensokyo.uk/security/fortify/helper/seccomp"
"git.gensokyo.uk/security/fortify/internal"
"git.gensokyo.uk/security/fortify/internal/app"
init0 "git.gensokyo.uk/security/fortify/internal/app/init"
"git.gensokyo.uk/security/fortify/internal/app/shim"
"git.gensokyo.uk/security/fortify/internal/fmsg"
"git.gensokyo.uk/security/fortify/internal/linux"
init0 "git.gensokyo.uk/security/fortify/internal/priv/init"
"git.gensokyo.uk/security/fortify/internal/priv/shim"
"git.gensokyo.uk/security/fortify/internal/state"
"git.gensokyo.uk/security/fortify/internal/system"
)
@ -38,8 +37,6 @@ var (
)
func init() {
fmsg.Prepare("fortify")
flag.BoolVar(&flagVerbose, "v", false, "Verbose output")
flag.BoolVar(&flagJSON, "json", false, "Format output in JSON when applicable")
}
@ -65,12 +62,13 @@ func main() {
init0.TryArgv0()
if err := internal.PR_SET_DUMPABLE__SUID_DUMP_DISABLE(); err != nil {
log.Printf("cannot set SUID_DUMP_DISABLE: %s", err)
fmsg.Printf("cannot set SUID_DUMP_DISABLE: %s", err)
// not fatal: this program runs as the privileged user
}
if os.Geteuid() == 0 {
log.Fatal("this program must not run as root")
fmsg.Fatal("this program must not run as root")
panic("unreachable")
}
flag.CommandLine.Usage = func() {
@ -98,12 +96,12 @@ func main() {
fmt.Println()
}
flag.Parse()
fmsg.Store(flagVerbose)
fmsg.SetVerbose(flagVerbose)
args := flag.Args()
if len(args) == 0 {
flag.CommandLine.Usage()
internal.Exit(0)
fmsg.Exit(0)
}
switch args[0] {
@ -113,20 +111,16 @@ func main() {
} else {
fmt.Println("impure")
}
internal.Exit(0)
fmsg.Exit(0)
case "license": // print embedded license
fmt.Println(license)
internal.Exit(0)
fmsg.Exit(0)
case "template": // print full template configuration
printJSON(os.Stdout, false, fst.Template())
internal.Exit(0)
fmsg.Exit(0)
case "help": // print help message
flag.CommandLine.Usage()
internal.Exit(0)
fmsg.Exit(0)
case "ps": // print all state info
set := flag.NewFlagSet("ps", flag.ExitOnError)
var short bool
@ -136,8 +130,7 @@ func main() {
_ = set.Parse(args[1:])
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(sys.Paths().RunDirPath), short)
internal.Exit(0)
fmsg.Exit(0)
case "show": // pretty-print app info
set := flag.NewFlagSet("show", flag.ExitOnError)
var short bool
@ -149,7 +142,6 @@ func main() {
switch len(set.Args()) {
case 0: // system
printShowSystem(os.Stdout, short)
case 1: // instance
name := set.Args()[0]
config, instance := tryShort(name)
@ -157,15 +149,14 @@ func main() {
config = tryPath(name)
}
printShowInstance(os.Stdout, time.Now().UTC(), instance, config, short)
default:
log.Fatal("show requires 1 argument")
fmsg.Fatal("show requires 1 argument")
}
internal.Exit(0)
fmsg.Exit(0)
case "app": // launch app from configuration file
if len(args) < 2 {
log.Fatal("app requires at least 1 argument")
fmsg.Fatal("app requires at least 1 argument")
}
// config extraArgs...
@ -175,7 +166,6 @@ func main() {
// invoke app
runApp(config)
panic("unreachable")
case "run": // run app in permissive defaults usage pattern
set := flag.NewFlagSet("run", flag.ExitOnError)
@ -218,7 +208,8 @@ func main() {
}
if aid < 0 || aid > 9999 {
log.Fatalf("aid %d out of range", aid)
fmsg.Fatalf("aid %d out of range", aid)
panic("unreachable")
}
// resolve home/username from os when flag is unset
@ -228,13 +219,13 @@ func main() {
passwdFunc = func() {
var us string
if uid, err := sys.Uid(aid); err != nil {
log.Fatalf("cannot obtain uid from fsu: %v", err)
fmsg.Fatalf("cannot obtain uid from fsu: %v", err)
} else {
us = strconv.Itoa(uid)
}
if u, err := user.LookupId(us); err != nil {
fmsg.Verbosef("cannot look up uid %s", us)
fmsg.VPrintf("cannot look up uid %s", us)
passwd = &user.User{
Uid: us,
Gid: us,
@ -276,7 +267,7 @@ func main() {
config.Confinement.SessionBus = dbus.NewConfig(fid, true, mpris)
} else {
if c, err := dbus.NewConfigFromFile(dbusConfigSession); err != nil {
log.Fatalf("cannot load session bus proxy config from %q: %s", dbusConfigSession, err)
fmsg.Fatalf("cannot load session bus proxy config from %q: %s", dbusConfigSession, err)
} else {
config.Confinement.SessionBus = c
}
@ -285,7 +276,7 @@ func main() {
// system bus proxy is optional
if dbusConfigSystem != "nil" {
if c, err := dbus.NewConfigFromFile(dbusConfigSystem); err != nil {
log.Fatalf("cannot load system bus proxy config from %q: %s", dbusConfigSystem, err)
fmsg.Fatalf("cannot load system bus proxy config from %q: %s", dbusConfigSystem, err)
} else {
config.Confinement.SystemBus = c
}
@ -300,18 +291,17 @@ func main() {
// invoke app
runApp(config)
panic("unreachable")
// internal commands
case "shim":
shim.Main()
internal.Exit(0)
fmsg.Exit(0)
case "init":
init0.Main()
internal.Exit(0)
fmsg.Exit(0)
default:
log.Fatalf("%q is not a valid command", args[0])
fmsg.Fatalf("%q is not a valid command", args[0])
}
panic("unreachable")
@ -323,15 +313,15 @@ func runApp(config *fst.Config) {
syscall.SIGINT, syscall.SIGTERM)
defer stop() // unreachable
if fmsg.Load() {
seccomp.CPrintln = log.Println
if fmsg.Verbose() {
seccomp.CPrintln = fmsg.Println
}
if a, err := app.New(sys); err != nil {
log.Fatalf("cannot create app: %s", err)
fmsg.Fatalf("cannot create app: %s\n", err)
} else if err = a.Seal(config); err != nil {
logBaseError(err, "cannot seal app:")
internal.Exit(1)
fmsg.Exit(1)
} else if err = a.Run(ctx, rs); err != nil {
if !rs.Start {
logBaseError(err, "cannot start app:")
@ -344,7 +334,8 @@ func runApp(config *fst.Config) {
}
}
if rs.WaitErr != nil {
log.Println("inner wait failed:", rs.WaitErr)
fmsg.Println("inner wait failed:", rs.WaitErr)
}
internal.Exit(rs.ExitCode)
fmsg.Exit(rs.ExitCode)
panic("unreachable")
}

View File

@ -36,7 +36,7 @@ package
*Default:*
` <derivation fortify-0.2.15> `
` <derivation fortify-0.2.14> `

View File

@ -16,7 +16,7 @@
buildGoModule rec {
pname = "fortify";
version = "0.2.15";
version = "0.2.14";
src = builtins.path {
name = "fortify-src";

View File

@ -4,7 +4,6 @@ import (
"encoding/json"
"errors"
"io"
"log"
"os"
"strconv"
"strings"
@ -22,10 +21,11 @@ func tryPath(name string) (config *fst.Config) {
if name != "-" {
r = tryFd(name)
if r == nil {
fmsg.Verbose("load configuration from file")
fmsg.VPrintln("load configuration from file")
if f, err := os.Open(name); err != nil {
log.Fatalf("cannot access configuration file %q: %s", name, err)
fmsg.Fatalf("cannot access configuration file %q: %s", name, err)
panic("unreachable")
} else {
// finalizer closes f
r = f
@ -33,7 +33,7 @@ func tryPath(name string) (config *fst.Config) {
} else {
defer func() {
if err := r.(io.ReadCloser).Close(); err != nil {
log.Printf("cannot close config fd: %v", err)
fmsg.Printf("cannot close config fd: %v", err)
}
}()
}
@ -42,7 +42,8 @@ func tryPath(name string) (config *fst.Config) {
}
if err := json.NewDecoder(r).Decode(&config); err != nil {
log.Fatalf("cannot load configuration: %v", err)
fmsg.Fatalf("cannot load configuration: %v", err)
panic("unreachable")
}
return
@ -50,7 +51,7 @@ func tryPath(name string) (config *fst.Config) {
func tryFd(name string) io.ReadCloser {
if v, err := strconv.Atoi(name); err != nil {
fmsg.Verbosef("name cannot be interpreted as int64: %v", err)
fmsg.VPrintf("name cannot be interpreted as int64: %v", err)
return nil
} else {
fd := uintptr(v)
@ -58,7 +59,7 @@ func tryFd(name string) io.ReadCloser {
if errors.Is(errno, syscall.EBADF) {
return nil
}
log.Fatalf("cannot get fd %d: %v", fd, errno)
fmsg.Fatalf("cannot get fd %d: %v", fd, errno)
}
return os.NewFile(fd, strconv.Itoa(v))
}
@ -82,11 +83,11 @@ func tryShort(name string) (config *fst.Config, instance *state.State) {
// try to match from state store
if likePrefix && len(name) >= 8 {
fmsg.Verbose("argument looks like prefix")
fmsg.VPrintln("argument looks like prefix")
s := state.NewMulti(sys.Paths().RunDirPath)
if entries, err := state.Join(s); err != nil {
log.Printf("cannot join store: %v", err)
fmsg.Printf("cannot join store: %v", err)
// drop to fetch from file
} else {
for id := range entries {
@ -98,7 +99,7 @@ func tryShort(name string) (config *fst.Config, instance *state.State) {
break
}
fmsg.Verbosef("instance %s skipped", v)
fmsg.VPrintf("instance %s skipped", v)
}
}
}

View File

@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"io"
"log"
"slices"
"strconv"
"strings"
@ -13,6 +12,7 @@ import (
"git.gensokyo.uk/security/fortify/dbus"
"git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/internal/fmsg"
"git.gensokyo.uk/security/fortify/internal/state"
)
@ -24,7 +24,7 @@ func printShowSystem(output io.Writer, short bool) {
// get fid by querying uid of aid 0
if uid, err := sys.Uid(0); err != nil {
log.Fatalf("cannot obtain uid from fsu: %v", err)
fmsg.Fatalf("cannot obtain uid from fsu: %v", err)
} else {
info.User = (uid / 10000) - 100
}
@ -190,12 +190,12 @@ func printShowInstance(
func printPs(output io.Writer, now time.Time, s state.Store, short bool) {
var entries state.Entries
if e, err := state.Join(s); err != nil {
log.Fatalf("cannot join store: %v", err)
fmsg.Fatalf("cannot join store: %v", err)
} else {
entries = e
}
if err := s.Close(); err != nil {
log.Printf("cannot close store: %v", err)
fmsg.Printf("cannot close store: %v", err)
}
if !short && flagJSON {
@ -212,13 +212,13 @@ func printPs(output io.Writer, now time.Time, s state.Store, short bool) {
for id, instance := range entries {
// gracefully skip nil states
if instance == nil {
log.Printf("got invalid state entry %s", id.String())
fmsg.Printf("got invalid state entry %s", id.String())
continue
}
// gracefully skip inconsistent states
if id != instance.ID {
log.Printf("possible store corruption: entry %s has id %s",
fmsg.Printf("possible store corruption: entry %s has id %s",
id.String(), instance.ID.String())
continue
}
@ -273,7 +273,8 @@ func printJSON(output io.Writer, short bool, v any) {
encoder.SetIndent("", " ")
}
if err := encoder.Encode(v); err != nil {
log.Fatalf("cannot serialise: %v", err)
fmsg.Fatalf("cannot serialise: %v", err)
panic("unreachable")
}
}
@ -283,26 +284,31 @@ type tp struct{ *tabwriter.Writer }
func (p *tp) Printf(format string, a ...any) {
if _, err := fmt.Fprintf(p, format, a...); err != nil {
log.Fatalf("cannot write to tabwriter: %v", err)
fmsg.Fatalf("cannot write to tabwriter: %v", err)
panic("unreachable")
}
}
func (p *tp) Println(a ...any) {
if _, err := fmt.Fprintln(p, a...); err != nil {
log.Fatalf("cannot write to tabwriter: %v", err)
fmsg.Fatalf("cannot write to tabwriter: %v", err)
panic("unreachable")
}
}
func (p *tp) MustFlush() {
if err := p.Writer.Flush(); err != nil {
log.Fatalf("cannot flush tabwriter: %v", err)
fmsg.Fatalf("cannot flush tabwriter: %v", err)
panic("unreachable")
}
}
func mustPrint(output io.Writer, a ...any) {
if _, err := fmt.Fprint(output, a...); err != nil {
log.Fatalf("cannot print: %v", err)
fmsg.Fatalf("cannot print: %v", err)
panic("unreachable")
}
}
func mustPrintln(output io.Writer, a ...any) {
if _, err := fmt.Fprintln(output, a...); err != nil {
log.Fatalf("cannot print: %v", err)
fmsg.Fatalf("cannot print: %v", err)
panic("unreachable")
}
}

View File

@ -3,7 +3,6 @@
self,
home-manager,
nixosTest,
fortify,
}:
nixosTest {
@ -111,18 +110,6 @@ nixosTest {
environment.fortify = {
enable = true;
package = fortify.overrideAttrs (previousAttrs: {
GOFLAGS = previousAttrs.GOFLAGS ++ [ "-race" ];
# fsu does not like cgo
disallowedReferences = previousAttrs.disallowedReferences ++ [ fortify ];
postInstall =
previousAttrs.postInstall
+ ''
cp -a "${fortify}/libexec/fsu" "$out/libexec/fsu"
sed -i 's:${fortify}:${placeholder "out"}:' "$out/libexec/fsu"
'';
});
stateDir = "/var/lib/fortify";
users.alice = 0;