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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -53,7 +53,7 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
} }
if s.Syscall == nil { 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 var uid int
@ -121,11 +121,11 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
// get parent dir of socket // get parent dir of socket
dir := path.Dir(pair[1]) dir := path.Dir(pair[1])
if dir == "." || dir == "/" { if dir == "." || dir == "/" {
fmsg.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) hidePaths = append(hidePaths, dir)
} else { } 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 return nil, err
} else if ok { } else if ok {
hidePathMatch[i] = true 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) { if !errors.Is(err, fs.ErrNotExist) {
return err return err
} }
fmsg.Verbosef("path %q does not yet exist", *v) fmsg.VPrintf("path %q does not yet exist", *v)
} else { } else {
*v = p *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) (--bind-data FD DEST)
*/ */
func (c *Config) CopyBind(dest string, payload []byte, opts ...bool) *Config { func (c *Config) CopyBind(dest string, payload []byte, opts ...bool) *Config {
var p *[]byte
c.CopyBindRef(dest, &p, opts...)
*p = payload
return c
}
// CopyBindRef is the same as CopyBind but writes the address of DataConfig.Data.
func (c *Config) CopyBindRef(dest string, payloadRef **[]byte, opts ...bool) *Config {
t := DataROBind t := DataROBind
if len(opts) > 0 && opts[0] { if len(opts) > 0 && opts[0] {
t = DataBind t = DataBind
} }
d := &DataConfig{Dest: dest, Type: t} c.Filesystem = append(c.Filesystem, &DataConfig{Dest: dest, Data: payload, Type: t})
*payloadRef = &d.Data
c.Filesystem = append(c.Filesystem, d)
return c return c
} }

View File

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

View File

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

View File

@ -4,12 +4,12 @@ import (
"crypto/sha512" "crypto/sha512"
"errors" "errors"
"io" "io"
"log"
"slices" "slices"
"syscall" "syscall"
"testing" "testing"
"git.gensokyo.uk/security/fortify/helper/seccomp" "git.gensokyo.uk/security/fortify/helper/seccomp"
"git.gensokyo.uk/security/fortify/internal/fmsg"
) )
func TestExport(t *testing.T) { func TestExport(t *testing.T) {
@ -79,7 +79,7 @@ func TestExport(t *testing.T) {
buf := make([]byte, 8) buf := make([]byte, 8)
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
seccomp.CPrintln = log.Println seccomp.CPrintln = fmsg.Println
t.Cleanup(func() { seccomp.CPrintln = nil }) t.Cleanup(func() { seccomp.CPrintln = nil })
e := seccomp.New(tc.opts) 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/bwrap"
"git.gensokyo.uk/security/fortify/helper/proc" "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; // InternalChildStub is an internal function but exported because it is cross-package;
@ -40,7 +40,7 @@ func InternalChildStub() {
genericStub(flagRestoreFiles(4, ap, sp)) genericStub(flagRestoreFiles(4, ap, sp))
} }
internal.Exit(0) fmsg.Exit(0)
} }
// InternalReplaceExecCommand is an internal function but exported because it is cross-package; // InternalReplaceExecCommand is an internal function but exported because it is cross-package;

View File

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

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). 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). 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"). 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{ MustProxyDBus("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", &dbus.Config{
Talk: []string{ Talk: []string{
"org.freedesktop.FileManager1", "org.freedesktop.Notifications", "org.freedesktop.FileManager1", "org.freedesktop.Notifications",
@ -213,7 +213,7 @@ var testCasesNixos = []sealTestCase{
CopyBind("/etc/group", []byte("fortify:x:1971:\n")). CopyBind("/etc/group", []byte("fortify:x:1971:\n")).
Bind("/run/user/1971/wayland-0", "/run/user/1971/wayland-0"). Bind("/run/user/1971/wayland-0", "/run/user/1971/wayland-0").
Bind("/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse", "/run/user/1971/pulse/native"). 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/bus", "/run/user/1971/bus").
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", "/run/dbus/system_bus_socket"). Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", "/run/dbus/system_bus_socket").
Tmpfs("/var/run/nscd", 8192). Tmpfs("/var/run/nscd", 8192).

View File

@ -219,7 +219,7 @@ var testCasesPd = []sealTestCase{
Ensure("/tmp/fortify.1971/wayland", 0711). Ensure("/tmp/fortify.1971/wayland", 0711).
Wayland("/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/1971/wayland-0", "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"). 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"). 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{ MustProxyDBus("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", &dbus.Config{
Talk: []string{ Talk: []string{
"org.freedesktop.Notifications", "org.freedesktop.Notifications",
@ -382,7 +382,7 @@ var testCasesPd = []sealTestCase{
CopyBind("/etc/group", []byte("fortify:x:65534:\n")). CopyBind("/etc/group", []byte("fortify:x:65534:\n")).
Bind("/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/65534/wayland-0"). Bind("/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/65534/wayland-0").
Bind("/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse", "/run/user/65534/pulse/native"). 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/bus", "/run/user/65534/bus").
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", "/run/dbus/system_bus_socket"). Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", "/run/dbus/system_bus_socket").
Tmpfs("/var/run/nscd", 8192). Tmpfs("/var/run/nscd", 8192).

View File

@ -191,7 +191,7 @@ func (a *app) Seal(config *fst.Config) error {
// map sandbox config to bwrap // map sandbox config to bwrap
if config.Confinement.Sandbox == nil { if config.Confinement.Sandbox == nil {
fmsg.Verbose("sandbox configuration not supplied, PROCEED WITH CAUTION") fmsg.VPrintln("sandbox configuration not supplied, PROCEED WITH CAUTION")
// permissive defaults // permissive defaults
conf := &fst.SandboxConfig{ conf := &fst.SandboxConfig{
@ -264,7 +264,7 @@ func (a *app) Seal(config *fst.Config) error {
} }
// verbose log seal information // verbose log seal information
fmsg.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.sys.user.us, seal.sys.user.username, config.Confinement.Groups, config.Command)
// seal app and release lock // 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) { if seal.et.Has(system.EWayland) {
var socketPath string var socketPath string
if name, ok := os.LookupEnv(wl.WaylandDisplay); !ok { if name, ok := os.LookupEnv(wl.WaylandDisplay); !ok {
fmsg.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) socketPath = path.Join(seal.RuntimePath, wl.FallbackName)
} else if !path.IsAbs(name) { } else if !path.IsAbs(name) {
socketPath = path.Join(seal.RuntimePath, name) socketPath = path.Join(seal.RuntimePath, name)
@ -166,7 +166,7 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error {
seal.sys.Wayland(outerPath, socketPath, appID, seal.id) seal.sys.Wayland(outerPath, socketPath, appID, seal.id)
seal.sys.bwrap.Bind(outerPath, innerPath) seal.sys.bwrap.Bind(outerPath, innerPath)
} else { // bind mount wayland socket (insecure) } else { // bind mount wayland socket (insecure)
fmsg.Verbose("direct wayland access, PROCEED WITH CAUTION") fmsg.VPrintln("direct wayland access, PROCEED WITH CAUTION")
seal.sys.bwrap.Bind(socketPath, innerPath) seal.sys.bwrap.Bind(socketPath, innerPath)
// ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`) // ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`)
@ -229,13 +229,13 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error {
// publish current user's pulse cookie for target user // publish current user's pulse cookie for target user
if src, err := discoverPulseCookie(os); err != nil { if src, err := discoverPulseCookie(os); err != nil {
// not fatal // not fatal
fmsg.Verbose(strings.TrimSpace(err.(*fmsg.BaseError).Message())) fmsg.VPrintln(strings.TrimSpace(err.(*fmsg.BaseError).Message()))
} else { } else {
dst := path.Join(seal.share, "pulse-cookie")
innerDst := fst.Tmp + "/pulse-cookie" innerDst := fst.Tmp + "/pulse-cookie"
seal.sys.bwrap.SetEnv[pulseCookie] = innerDst seal.sys.bwrap.SetEnv[pulseCookie] = innerDst
payload := new([]byte) seal.sys.CopyFile(dst, src)
seal.sys.bwrap.CopyBindRef(innerDst, &payload) seal.sys.bwrap.Bind(dst, innerDst)
seal.sys.CopyFile(payload, src, 256, 256)
} }
} }

View File

@ -4,15 +4,14 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"log"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
"git.gensokyo.uk/security/fortify/helper" "git.gensokyo.uk/security/fortify/helper"
"git.gensokyo.uk/security/fortify/internal/app/shim"
"git.gensokyo.uk/security/fortify/internal/fmsg" "git.gensokyo.uk/security/fortify/internal/fmsg"
"git.gensokyo.uk/security/fortify/internal/priv/shim"
"git.gensokyo.uk/security/fortify/internal/state" "git.gensokyo.uk/security/fortify/internal/state"
"git.gensokyo.uk/security/fortify/internal/system" "git.gensokyo.uk/security/fortify/internal/system"
) )
@ -82,7 +81,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
Bwrap: a.seal.sys.bwrap, Bwrap: a.seal.sys.bwrap,
Home: a.seal.sys.user.data, Home: a.seal.sys.user.data,
Verbose: fmsg.Load(), Verbose: fmsg.Verbose(),
}); err != nil { }); err != nil {
return err return err
} }
@ -120,8 +119,8 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
} else { } else {
rs.ExitCode = a.shim.Unwrap().ProcessState.ExitCode() rs.ExitCode = a.shim.Unwrap().ProcessState.ExitCode()
} }
if fmsg.Load() { if fmsg.Verbose() {
fmsg.Verbosef("process %d exited with exit code %d", a.shim.Unwrap().Process.Pid, rs.ExitCode) 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 // 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 // the effects of this is similar to the alternative exit path and ensures shim death
case err := <-a.shim.WaitFallback(): case err := <-a.shim.WaitFallback():
rs.ExitCode = 255 rs.ExitCode = 255
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 // alternative exit path relying on shim behaviour on monitor process exit
case <-ctx.Done(): case <-ctx.Done():
fmsg.Verbose("alternative exit path selected") fmsg.VPrintln("alternative exit path selected")
} }
// child process exited, resume output // child process exited, resume output
@ -164,10 +163,10 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
} else { } else {
if l := len(states); l == 0 { if l := len(states); l == 0 {
// cleanup globals as the final launcher // cleanup globals as the final launcher
fmsg.Verbose("no other launchers active, will clean up globals") fmsg.VPrintln("no other launchers active, will clean up globals")
ec.Set(system.User) ec.Set(system.User)
} else { } 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 // accumulate capabilities of other launchers
@ -175,7 +174,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
if s.Config != nil { if s.Config != nil {
*rt |= s.Config.Confinement.Enablements *rt |= s.Config.Confinement.Enablements
} else { } else {
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) ec.Set(i)
} }
} }
if fmsg.Load() { if fmsg.Verbose() {
labels := make([]string, 0, system.ELen+1) labels := make([]string, 0, system.ELen+1)
for i := system.Enablement(0); i < system.Enablement(system.ELen+2); i++ { for i := system.Enablement(0); i < system.Enablement(system.ELen+2); i++ {
if ec.Has(i) { if ec.Has(i) {
@ -193,7 +192,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
} }
} }
if len(labels) > 0 { 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 package fmsg
import ( import (
"bytes"
"io"
"log" "log"
"os" "os"
"sync"
"sync/atomic"
"syscall"
) )
const ( var std = log.New(os.Stderr, "fortify: ", 0)
bufSize = 4 * 1024
bufSizeMax = 16 * 1024 * 1024
)
var o = &suspendable{w: os.Stderr} func SetPrefix(prefix string) {
prefix += ": "
// Prepare configures the system logger for [Suspend] and [Resume] to take effect. std.SetPrefix(prefix)
func Prepare(prefix string) { log.SetPrefix(prefix + ": "); log.SetFlags(0); log.SetOutput(o) } std.SetPrefix(prefix)
type suspendable struct {
w io.Writer
s atomic.Bool
buf bytes.Buffer
bufOnce sync.Once
bufMu sync.Mutex
dropped int
} }
func (s *suspendable) Write(p []byte) (n int, err error) { func Print(v ...any) {
if !s.s.Load() { dequeueOnce.Do(dequeue)
return s.w.Write(p) queue(dPrint(v))
}
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 (s *suspendable) prepareBuf() { s.buf.Grow(bufSize) } func Printf(format string, v ...any) {
func (s *suspendable) Suspend() bool { return o.s.CompareAndSwap(false, true) } dequeueOnce.Do(dequeue)
func (s *suspendable) Resume() (resumed bool, dropped uintptr, n int64, err error) { queue(&dPrintf{format, v})
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 Suspend() bool { return o.Suspend() } func Println(v ...any) {
func Resume() bool { dequeueOnce.Do(dequeue)
resumed, dropped, _, err := o.Resume() queue(dPrintln(v))
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 BeforeExit() { func Fatal(v ...any) {
if Resume() { Print(v...)
log.Printf("beforeExit reached on suspended output") Exit(1)
} }
func Fatalf(format string, v ...any) {
Printf(format, v...)
Exit(1)
} }

View File

@ -1,23 +1,25 @@
package fmsg package fmsg
import ( import "sync/atomic"
"log"
"sync/atomic"
)
var verbose = new(atomic.Bool) var verbose = new(atomic.Bool)
func Load() bool { return verbose.Load() } func Verbose() bool {
func Store(v bool) { verbose.Store(v) } 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() { if verbose.Load() {
log.Printf(format, v...) Printf(format, v...)
} }
} }
func Verbose(v ...any) { func VPrintln(v ...any) {
if verbose.Load() { if verbose.Load() {
log.Println(v...) Println(v...)
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,6 @@ package system
import ( import (
"bytes" "bytes"
"errors" "errors"
"log"
"strings" "strings"
"sync" "sync"
@ -48,12 +47,12 @@ func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath st
d.proxy = dbus.New(sessionBus, systemBus) d.proxy = dbus.New(sessionBus, systemBus)
defer func() { defer func() {
if fmsg.Load() && d.proxy.Sealed() { if fmsg.Verbose() && d.proxy.Sealed() {
fmsg.Verbose("sealed session proxy", session.Args(sessionBus)) fmsg.VPrintln("sealed session proxy", session.Args(sessionBus))
if system != nil { 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 { 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 { 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 // this starts the process and blocks until ready
@ -90,15 +89,15 @@ func (d *DBus) apply(sys *I) error {
return fmsg.WrapErrorSuffix(err, return fmsg.WrapErrorSuffix(err,
"cannot start message bus proxy:") "cannot start message bus proxy:")
} }
fmsg.Verbose("starting message bus proxy:", d.proxy) fmsg.VPrintln("starting message bus proxy:", d.proxy)
return nil return nil
} }
func (d *DBus) revert(_ *I, _ *Criteria) error { func (d *DBus) revert(_ *I, _ *Criteria) error {
// criteria ignored here since dbus is always process-scoped // criteria ignored here since dbus is always process-scoped
fmsg.Verbose("terminating message bus proxy") fmsg.VPrintln("terminating message bus proxy")
d.proxy.Close() 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:") 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() { func (s *scanToFmsg) Dump() {
s.mu.RLock() s.mu.RLock()
for _, msg := range s.msgbuf { for _, msg := range s.msgbuf {
log.Println(msg) fmsg.Println(msg)
} }
s.mu.RUnlock() 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 { func (m *Mkdir) apply(_ *I) error {
fmsg.Verbose("ensuring directory", m) fmsg.VPrintln("ensuring directory", m)
// create directory // create directory
err := os.Mkdir(m.path, m.perm) err := os.Mkdir(m.path, m.perm)
@ -61,11 +61,11 @@ func (m *Mkdir) revert(_ *I, ec *Criteria) error {
} }
if ec.hasType(m) { if ec.hasType(m) {
fmsg.Verbose("destroying ephemeral directory", m) fmsg.VPrintln("destroying ephemeral directory", m)
return fmsg.WrapErrorSuffix(os.Remove(m.path), return fmsg.WrapErrorSuffix(os.Remove(m.path),
fmt.Sprintf("cannot remove ephemeral directory %q:", m.path)) fmt.Sprintf("cannot remove ephemeral directory %q:", m.path))
} else { } else {
fmsg.Verbose("skipping ephemeral directory", m) fmsg.VPrintln("skipping ephemeral directory", m)
return nil return nil
} }
} }

View File

@ -3,7 +3,6 @@ package system
import ( import (
"context" "context"
"errors" "errors"
"log"
"os" "os"
"sync" "sync"
@ -106,9 +105,9 @@ func (sys *I) Commit(ctx context.Context) error {
// sp is set to nil when all ops are applied // sp is set to nil when all ops are applied
if sp != nil { if sp != nil {
// rollback partial commit // rollback partial commit
fmsg.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 { 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", "op type mismatch",
system.New(150). system.New(150).
ChangeHosts("chronos"). 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). system.New(150).
ChangeHosts("chronos"). ChangeHosts("chronos").
Ensure("/run", 0755), Ensure("/run", 0755),

View File

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

View File

@ -1,29 +1,50 @@
package system package system
import ( import (
"strconv"
"testing" "testing"
"git.gensokyo.uk/security/fortify/acl"
) )
func TestCopyFile(t *testing.T) { func TestCopyFile(t *testing.T) {
testCases := []struct { testCases := []struct {
tcOp dst, src string
cap int
n int64
}{ }{
{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 { 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 := New(150)
sys.CopyFile(new([]byte), tc.path, tc.cap, tc.n) sys.CopyFile(tc.dst, tc.src)
tc.test(t, sys.ops, []Op{ (&tcOp{Process, tc.src}).test(t, sys.ops, []Op{
&Tmpfile{nil, tc.path, tc.n, nil}, &Tmpfile{Process, tmpfileCopy, tc.dst, tc.src},
&ACL{Process, tc.dst, []acl.Perm{acl.Read}},
}, "CopyFile") }, "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) { func TestLink(t *testing.T) {
testCases := []struct { testCases := []struct {
dst, src string dst, src string
@ -36,7 +57,7 @@ func TestLink(t *testing.T) {
sys := New(150) sys := New(150)
sys.Link(tc.src, tc.dst) sys.Link(tc.src, tc.dst)
(&tcOp{Process, tc.src}).test(t, sys.ops, []Op{ (&tcOp{Process, tc.src}).test(t, sys.ops, []Op{
&Hardlink{Process, tc.dst, tc.src}, &Tmpfile{Process, tmpfileLink, tc.dst, tc.src},
}, "Link") }, "Link")
}) })
} }
@ -55,25 +76,44 @@ func TestLinkFileType(t *testing.T) {
sys := New(150) sys := New(150)
sys.LinkFileType(tc.et, tc.path, tc.dst) sys.LinkFileType(tc.et, tc.path, tc.dst)
tc.test(t, sys.ops, []Op{ tc.test(t, sys.ops, []Op{
&Hardlink{tc.et, tc.dst, tc.path}, &Tmpfile{tc.et, tmpfileLink, tc.dst, tc.path},
}, "LinkFileType") }, "LinkFileType")
}) })
} }
} }
func TestTmpfile_String(t *testing.T) { 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 { testCases := []struct {
src string method uint8
n int64 dst, src string
want string want string
}{ }{
{"/home/ophestra/xdg/config/pulse/cookie", 256, {tmpfileCopy, "/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie",
`up to 256 bytes from "/home/ophestra/xdg/config/pulse/cookie"`}, `"/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse-cookie" from "/home/ophestra/xdg/config/pulse/cookie"`},
{tmpfileLink, "/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/wayland", "/run/user/1971/wayland-0",
`"/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/wayland" from "/run/user/1971/wayland-0"`},
{tmpfileLink, "/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse", "/run/user/1971/pulse/native",
`"/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse" from "/run/user/1971/pulse/native"`},
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.want, func(t *testing.T) { 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) 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, return fmsg.WrapErrorSuffix(err,
fmt.Sprintf("cannot attach to wayland on %q:", w.pair[1])) fmt.Sprintf("cannot attach to wayland on %q:", w.pair[1]))
} else { } else {
fmsg.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 { if sp, err := w.conn.Bind(w.pair[0], w.appID, w.instanceID); err != nil {
@ -53,7 +53,7 @@ func (w Wayland) apply(sys *I) error {
fmt.Sprintf("cannot bind to socket on %q:", w.pair[0])) fmt.Sprintf("cannot bind to socket on %q:", w.pair[0]))
} else { } else {
sys.sp = sp sys.sp = sp
fmsg.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)), return fmsg.WrapErrorSuffix(errors.Join(os.Chmod(w.pair[0], 0), acl.UpdatePerm(w.pair[0], sys.uid, acl.Read, acl.Write, acl.Execute)),
fmt.Sprintf("cannot chmod socket on %q:", w.pair[0])) fmt.Sprintf("cannot chmod socket on %q:", w.pair[0]))
} }
@ -61,16 +61,16 @@ func (w Wayland) apply(sys *I) error {
func (w Wayland) revert(_ *I, ec *Criteria) error { func (w Wayland) revert(_ *I, ec *Criteria) error {
if ec.hasType(w) { if ec.hasType(w) {
fmsg.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) { if err := os.Remove(w.pair[0]); err != nil && !errors.Is(err, os.ErrNotExist) {
return err 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(), return fmsg.WrapErrorSuffix(w.conn.Close(),
fmt.Sprintf("cannot detach from wayland on %q:", w.pair[1])) fmt.Sprintf("cannot detach from wayland on %q:", w.pair[1]))
} else { } else {
fmsg.Verbosef("skipping wayland cleanup on %q", w.pair[0]) fmsg.VPrintf("skipping wayland cleanup on %q", w.pair[0])
return nil return nil
} }
} }

View File

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

71
main.go
View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,6 @@
self, self,
home-manager, home-manager,
nixosTest, nixosTest,
fortify,
}: }:
nixosTest { nixosTest {
@ -111,18 +110,6 @@ nixosTest {
environment.fortify = { environment.fortify = {
enable = true; 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"; stateDir = "/var/lib/fortify";
users.alice = 0; users.alice = 0;