Compare commits
9 Commits
90b86a5531
...
83e72c2b59
Author | SHA1 | Date | |
---|---|---|---|
83e72c2b59 | |||
82a072f641 | |||
60c10c3f4a | |||
468696f611 | |||
29c38caac8 | |||
e599b5583d | |||
33a4ab11c2 | |||
1fa5e992e4 | |||
c667b13a00 |
@ -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()
|
||||||
fmsg.Fatalf("cannot open bundle: %v", err)
|
log.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()
|
||||||
fmsg.Fatalf("cannot parse bundle metadata: %v", err)
|
log.Fatalf("cannot parse bundle metadata: %v", err)
|
||||||
} else if err = f.Close(); err != nil {
|
} else if err = f.Close(); err != nil {
|
||||||
fmsg.Printf("cannot close bundle metadata: %v", err)
|
log.Printf("cannot close bundle metadata: %v", err)
|
||||||
// not fatal
|
// not fatal
|
||||||
}
|
}
|
||||||
|
|
||||||
if bundle.ID == "" {
|
if bundle.ID == "" {
|
||||||
beforeFail()
|
beforeFail()
|
||||||
fmsg.Fatal("application identifier must not be empty")
|
log.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 {
|
||||||
fmsg.Printf("cannot get hostname: %v", err)
|
log.Printf("cannot get hostname: %v", err)
|
||||||
return "fortify-" + name
|
return "fortify-" + name
|
||||||
} else {
|
} else {
|
||||||
return h + "-" + name
|
return h + "-" + name
|
||||||
|
@ -3,10 +3,12 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,12 +27,12 @@ func actionInstall(args []string) {
|
|||||||
args = set.Args()
|
args = set.Args()
|
||||||
|
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
fmsg.Fatal("invalid argument")
|
log.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 {
|
||||||
fmsg.Fatalf("cannot get current directory: %v", err)
|
log.Fatalf("cannot get current directory: %v", err)
|
||||||
} else {
|
} else {
|
||||||
pkgPath = path.Join(dir, pkgPath)
|
pkgPath = path.Join(dir, pkgPath)
|
||||||
}
|
}
|
||||||
@ -54,7 +56,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 {
|
||||||
fmsg.Fatalf("cannot create temporary directory: %v", err)
|
log.Fatalf("cannot create temporary directory: %v", err)
|
||||||
} else {
|
} else {
|
||||||
workDir = p
|
workDir = p
|
||||||
}
|
}
|
||||||
@ -78,19 +80,17 @@ 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()
|
||||||
fmsg.Fatalf("cannot access %q: %v", pathSet.metaPath, err)
|
log.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()
|
||||||
fmsg.Fatalf("metadata path %q is not a file", pathSet.metaPath)
|
log.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()
|
||||||
fmsg.Fatalf("app %q claims to have identifier %q", bundle.ID, app.ID)
|
log.Fatalf("app %q claims to have identifier %q", bundle.ID, app.ID)
|
||||||
}
|
}
|
||||||
// sec: should verify credentials
|
// sec: should verify credentials
|
||||||
}
|
}
|
||||||
@ -102,21 +102,20 @@ func actionInstall(args []string) {
|
|||||||
app.Launcher == bundle.Launcher &&
|
app.Launcher == bundle.Launcher &&
|
||||||
app.ActivationPackage == bundle.ActivationPackage {
|
app.ActivationPackage == bundle.ActivationPackage {
|
||||||
cleanup()
|
cleanup()
|
||||||
fmsg.Printf("package %q is identical to local application %q", pkgPath, app.ID)
|
log.Printf("package %q is identical to local application %q", pkgPath, app.ID)
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppID determines uid
|
// AppID determines uid
|
||||||
if app.AppID != bundle.AppID {
|
if app.AppID != bundle.AppID {
|
||||||
cleanup()
|
cleanup()
|
||||||
fmsg.Fatalf("package %q app id %d differs from installed %d", pkgPath, bundle.AppID, app.AppID)
|
log.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.VPrintf("installing application %q version %q over local %q", bundle.ID, bundle.Version, app.Version)
|
fmsg.Verbosef("installing application %q version %q over local %q", bundle.ID, bundle.Version, app.Version)
|
||||||
} else {
|
} else {
|
||||||
fmsg.VPrintf("application %q clean installation", bundle.ID)
|
fmsg.Verbosef("application %q clean installation", bundle.ID)
|
||||||
// sec: should install credentials
|
// sec: should install credentials
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,21 +173,18 @@ 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()
|
||||||
fmsg.Fatalf("cannot create metadata file: %v", err)
|
log.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()
|
||||||
fmsg.Fatalf("cannot write metadata: %v", err)
|
log.Fatalf("cannot write metadata: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
} else if err = f.Close(); err != nil {
|
} else if err = f.Close(); err != nil {
|
||||||
fmsg.Printf("cannot close metadata file: %v", err)
|
log.Printf("cannot close metadata file: %v", err)
|
||||||
// not fatal
|
// not fatal
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Rename(pathSet.metaPath+"~", pathSet.metaPath); err != nil {
|
if err := os.Rename(pathSet.metaPath+"~", pathSet.metaPath); err != nil {
|
||||||
cleanup()
|
cleanup()
|
||||||
fmsg.Fatalf("cannot rename metadata file: %v", err)
|
log.Fatalf("cannot rename metadata file: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup()
|
cleanup()
|
||||||
|
@ -2,8 +2,10 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -11,7 +13,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 {
|
||||||
fmsg.Fatalf("cannot set $SHELL: %v", err)
|
log.Fatalf("cannot set $SHELL: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,14 +26,14 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmsg.SetPrefix("fpkg")
|
fmsg.Prepare("fpkg")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
fmsg.SetVerbose(flagVerbose)
|
fmsg.Store(flagVerbose)
|
||||||
|
|
||||||
args := flag.Args()
|
args := flag.Args()
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
fmsg.Fatal("invalid argument")
|
log.Fatal("invalid argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
@ -41,8 +43,8 @@ func main() {
|
|||||||
actionStart(args[1:])
|
actionStart(args[1:])
|
||||||
|
|
||||||
default:
|
default:
|
||||||
fmsg.Fatal("invalid argument")
|
log.Fatal("invalid argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
@ -25,8 +26,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 {
|
||||||
fmsg.Fatalf("%s: command not found", file)
|
log.Fatalf("%s: command not found", file)
|
||||||
panic("unreachable")
|
return ""
|
||||||
} else {
|
} else {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
@ -35,15 +36,14 @@ 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.VPrintf("spawning process: %q %q", name, arg)
|
fmsg.Verbosef("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)()
|
||||||
}
|
}
|
||||||
fmsg.Fatalf("%s: %v", name, err)
|
log.Fatalf("%s: %v", name, err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
@ -25,14 +26,12 @@ func fortifyApp(config *fst.Config, beforeFail func()) {
|
|||||||
)
|
)
|
||||||
if p, ok := internal.Path(Fmain); !ok {
|
if p, ok := internal.Path(Fmain); !ok {
|
||||||
beforeFail()
|
beforeFail()
|
||||||
fmsg.Fatal("invalid fortify path, this copy of fpkg is not compiled correctly")
|
log.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()
|
||||||
fmsg.Fatalf("cannot pipe: %v", err)
|
log.Fatalf("cannot pipe: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
} else {
|
} else {
|
||||||
if fmsg.Verbose() {
|
if fmsg.Load() {
|
||||||
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")
|
||||||
@ -45,26 +44,22 @@ 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()
|
||||||
fmsg.Fatalf("cannot send configuration: %v", err)
|
log.Fatalf("cannot send configuration: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
beforeFail()
|
beforeFail()
|
||||||
fmsg.Fatalf("cannot start fortify: %v", err)
|
log.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()
|
||||||
fmsg.Exit(exitError.ExitCode())
|
internal.Exit(exitError.ExitCode())
|
||||||
panic("unreachable")
|
|
||||||
} else {
|
} else {
|
||||||
beforeFail()
|
beforeFail()
|
||||||
fmsg.Fatalf("cannot wait: %v", err)
|
log.Fatalf("cannot wait: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,12 @@ 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/fmsg"
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
func actionStart(args []string) {
|
func actionStart(args []string) {
|
||||||
@ -26,7 +27,7 @@ func actionStart(args []string) {
|
|||||||
args = set.Args()
|
args = set.Args()
|
||||||
|
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
fmsg.Fatal("invalid argument")
|
log.Fatal("invalid argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -37,7 +38,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 {
|
||||||
fmsg.Fatalf("app %q claims to have identifier %q", id, app.ID)
|
log.Fatalf("app %q claims to have identifier %q", id, app.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -144,7 +145,7 @@ func actionStart(args []string) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
fortifyApp(config, func() {})
|
fortifyApp(config, func() {})
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendGPUFilesystem(config *fst.Config) {
|
func appendGPUFilesystem(config *fst.Config) {
|
||||||
|
@ -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/fmsg"
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
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()
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
}
|
}
|
||||||
fortifyApp(config, beforeFail)
|
fortifyApp(config, beforeFail)
|
||||||
}
|
}
|
||||||
|
15
error.go
15
error.go
@ -2,6 +2,7 @@ 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"
|
||||||
@ -10,13 +11,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) {
|
||||||
fmsg.Println("wait failed:", err)
|
log.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
|
||||||
fmsg.Print(e.Message())
|
log.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
|
||||||
@ -24,7 +25,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
|
||||||
fmsg.Print(e.Message())
|
log.Print(e.Message())
|
||||||
} else {
|
} else {
|
||||||
errs := ej.Unwrap()
|
errs := ej.Unwrap()
|
||||||
|
|
||||||
@ -33,10 +34,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
|
||||||
fmsg.Println("invalid error type returned by revert:", ei)
|
log.Println("invalid error type returned by revert:", ei)
|
||||||
} else {
|
} else {
|
||||||
// print inner *app.BaseError message
|
// print inner *app.BaseError message
|
||||||
fmsg.Print(eb.Message())
|
log.Print(eb.Message())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,8 +49,8 @@ func logBaseError(err error, message string) {
|
|||||||
var e *fmsg.BaseError
|
var e *fmsg.BaseError
|
||||||
|
|
||||||
if fmsg.AsBaseError(err, &e) {
|
if fmsg.AsBaseError(err, &e) {
|
||||||
fmsg.Print(e.Message())
|
log.Print(e.Message())
|
||||||
} else {
|
} else {
|
||||||
fmsg.Println(message, err)
|
log.Println(message, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,10 @@
|
|||||||
touch $out
|
touch $out
|
||||||
'';
|
'';
|
||||||
|
|
||||||
nixos-tests = callPackage ./test.nix { inherit system self home-manager; };
|
nixos-tests = callPackage ./test.nix {
|
||||||
|
inherit system self home-manager;
|
||||||
|
inherit (self.packages.${system}) fortify;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s.Syscall == nil {
|
if s.Syscall == nil {
|
||||||
fmsg.VPrintln("syscall filter not configured, PROCEED WITH CAUTION")
|
fmsg.Verbose("syscall filter not configured, PROCEED WITH CAUTION")
|
||||||
}
|
}
|
||||||
|
|
||||||
var uid int
|
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.VPrintf("dbus socket %q is in an unusual location", pair[1])
|
fmsg.Verbosef("dbus socket %q is in an unusual location", pair[1])
|
||||||
}
|
}
|
||||||
hidePaths = append(hidePaths, dir)
|
hidePaths = append(hidePaths, dir)
|
||||||
} else {
|
} else {
|
||||||
fmsg.VPrintf("dbus socket %q is not absolute", pair[1])
|
fmsg.Verbosef("dbus socket %q is not absolute", pair[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,7 +169,7 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
} else if ok {
|
} else if ok {
|
||||||
hidePathMatch[i] = true
|
hidePathMatch[i] = true
|
||||||
fmsg.VPrintf("hiding paths from %q", c.Src)
|
fmsg.Verbosef("hiding paths from %q", c.Src)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,7 +221,7 @@ func evalSymlinks(os linux.System, v *string) error {
|
|||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmsg.VPrintf("path %q does not yet exist", *v)
|
fmsg.Verbosef("path %q does not yet exist", *v)
|
||||||
} else {
|
} else {
|
||||||
*v = p
|
*v = p
|
||||||
}
|
}
|
||||||
|
@ -78,11 +78,22 @@ 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
|
||||||
}
|
}
|
||||||
c.Filesystem = append(c.Filesystem, &DataConfig{Dest: dest, Data: payload, Type: t})
|
d := &DataConfig{Dest: dest, Type: t}
|
||||||
|
*payloadRef = &d.Data
|
||||||
|
|
||||||
|
c.Filesystem = append(c.Filesystem, d)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package bwrap_test
|
package bwrap_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
@ -8,11 +9,10 @@ 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 = fmsg.Println
|
seccomp.CPrintln = log.Println
|
||||||
t.Cleanup(func() { seccomp.CPrintln = nil })
|
t.Cleanup(func() { seccomp.CPrintln = nil })
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
@ -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 = fmsg.Println
|
seccomp.CPrintln = log.Println
|
||||||
t.Cleanup(func() { seccomp.CPrintln = nil })
|
t.Cleanup(func() { seccomp.CPrintln = nil })
|
||||||
|
|
||||||
e := seccomp.New(tc.opts)
|
e := seccomp.New(tc.opts)
|
||||||
|
@ -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/fmsg"
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmsg.Exit(0)
|
internal.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;
|
||||||
|
@ -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 {
|
||||||
|
@ -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("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie").
|
CopyFile(nil, "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
|
||||||
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").
|
||||||
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/pulse-cookie", fst.Tmp+"/pulse-cookie").
|
CopyBind(fst.Tmp+"/pulse-cookie", nil).
|
||||||
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).
|
||||||
|
@ -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("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie").
|
CopyFile(new([]byte), "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
|
||||||
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").
|
||||||
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/pulse-cookie", fst.Tmp+"/pulse-cookie").
|
CopyBind(fst.Tmp+"/pulse-cookie", nil).
|
||||||
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).
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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()
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,6 +2,7 @@ package init0
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@ -24,17 +25,15 @@ const (
|
|||||||
func Main() {
|
func Main() {
|
||||||
// sharing stdout with shim
|
// sharing stdout with shim
|
||||||
// USE WITH CAUTION
|
// USE WITH CAUTION
|
||||||
fmsg.SetPrefix("init")
|
fmsg.Prepare("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 {
|
||||||
fmsg.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
|
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.Getpid() != 1 {
|
if os.Getpid() != 1 {
|
||||||
fmsg.Fatal("this process must run as pid 1")
|
log.Fatal("this process must run as pid 1")
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// receive setup payload
|
// receive setup payload
|
||||||
@ -44,30 +43,29 @@ 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) {
|
||||||
fmsg.Fatal("invalid config descriptor")
|
log.Fatal("invalid config descriptor")
|
||||||
}
|
}
|
||||||
if errors.Is(err, proc.ErrNotSet) {
|
if errors.Is(err, proc.ErrNotSet) {
|
||||||
fmsg.Fatal("FORTIFY_INIT not set")
|
log.Fatal("FORTIFY_INIT not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmsg.Fatalf("cannot decode init setup payload: %v", err)
|
log.Fatalf("cannot decode init setup payload: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
} else {
|
} else {
|
||||||
fmsg.SetVerbose(payload.Verbose)
|
fmsg.Store(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 {
|
||||||
fmsg.Printf("cannot unset %s: %v", Env, err)
|
log.Printf("cannot unset %s: %v", Env, err)
|
||||||
// not fatal
|
// not fatal
|
||||||
} else {
|
} else {
|
||||||
fmsg.VPrintln("received configuration")
|
fmsg.Verbose("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 {
|
||||||
fmsg.Fatalf("prctl(PR_SET_PDEATHSIG, SIGKILL): %v", err)
|
log.Fatalf("prctl(PR_SET_PDEATHSIG, SIGKILL): %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(payload.Argv0)
|
cmd := exec.Command(payload.Argv0)
|
||||||
@ -76,13 +74,13 @@ func Main() {
|
|||||||
cmd.Env = os.Environ()
|
cmd.Env = os.Environ()
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
fmsg.Fatalf("cannot start %q: %v", payload.Argv0, err)
|
log.Fatalf("cannot start %q: %v", payload.Argv0, err)
|
||||||
}
|
}
|
||||||
fmsg.Suspend()
|
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 {
|
||||||
fmsg.Println("cannot close setup pipe:", err)
|
log.Println("cannot close setup pipe:", err)
|
||||||
// not fatal
|
// not fatal
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +117,7 @@ func Main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !errors.Is(err, syscall.ECHILD) {
|
if !errors.Is(err, syscall.ECHILD) {
|
||||||
fmsg.Println("unexpected wait4 response:", err)
|
log.Println("unexpected wait4 response:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
close(done)
|
close(done)
|
||||||
@ -132,9 +130,12 @@ func Main() {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case s := <-sig:
|
case s := <-sig:
|
||||||
fmsg.VPrintln("received", s.String())
|
if fmsg.Resume() {
|
||||||
fmsg.Resume() // output could still be withheld at this point, so resume is called
|
fmsg.Verbosef("terminating on %s after process start", s.String())
|
||||||
fmsg.Exit(0)
|
} else {
|
||||||
|
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
|
||||||
@ -155,10 +156,10 @@ func Main() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
case <-done:
|
case <-done:
|
||||||
fmsg.Exit(r)
|
internal.Exit(r)
|
||||||
case <-timeout:
|
case <-timeout:
|
||||||
fmsg.Println("timeout exceeded waiting for lingering processes")
|
log.Println("timeout exceeded waiting for lingering processes")
|
||||||
fmsg.Exit(r)
|
internal.Exit(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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.VPrintln("sandbox configuration not supplied, PROCEED WITH CAUTION")
|
fmsg.Verbose("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.VPrintf("created application seal for uid %s (%s) groups: %v, command: %s",
|
fmsg.Verbosef("created application seal for uid %s (%s) groups: %v, command: %s",
|
||||||
seal.sys.user.us, seal.sys.user.username, config.Confinement.Groups, config.Command)
|
seal.sys.user.us, seal.sys.user.username, config.Confinement.Groups, config.Command)
|
||||||
|
|
||||||
// seal app and release lock
|
// seal app and release lock
|
||||||
|
@ -143,7 +143,7 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error {
|
|||||||
if seal.et.Has(system.EWayland) {
|
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.VPrintln(wl.WaylandDisplay + " is not set, assuming " + wl.FallbackName)
|
fmsg.Verbose(wl.WaylandDisplay + " is not set, assuming " + wl.FallbackName)
|
||||||
socketPath = path.Join(seal.RuntimePath, wl.FallbackName)
|
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.VPrintln("direct wayland access, PROCEED WITH CAUTION")
|
fmsg.Verbose("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.VPrintln(strings.TrimSpace(err.(*fmsg.BaseError).Message()))
|
fmsg.Verbose(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
|
||||||
seal.sys.CopyFile(dst, src)
|
payload := new([]byte)
|
||||||
seal.sys.bwrap.Bind(dst, innerDst)
|
seal.sys.bwrap.CopyBindRef(innerDst, &payload)
|
||||||
|
seal.sys.CopyFile(payload, src, 256, 256)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package shim
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@ -15,8 +16,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
|
||||||
@ -25,12 +26,11 @@ import (
|
|||||||
func Main() {
|
func Main() {
|
||||||
// sharing stdout with fortify
|
// sharing stdout with fortify
|
||||||
// USE WITH CAUTION
|
// USE WITH CAUTION
|
||||||
fmsg.SetPrefix("shim")
|
fmsg.Prepare("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 {
|
||||||
fmsg.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
|
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// receive setup payload
|
// receive setup payload
|
||||||
@ -40,21 +40,20 @@ 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) {
|
||||||
fmsg.Fatal("invalid config descriptor")
|
log.Fatal("invalid config descriptor")
|
||||||
}
|
}
|
||||||
if errors.Is(err, proc.ErrNotSet) {
|
if errors.Is(err, proc.ErrNotSet) {
|
||||||
fmsg.Fatal("FORTIFY_SHIM not set")
|
log.Fatal("FORTIFY_SHIM not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmsg.Fatalf("cannot decode shim setup payload: %v", err)
|
log.Fatalf("cannot decode shim setup payload: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
} else {
|
} else {
|
||||||
fmsg.SetVerbose(payload.Verbose)
|
fmsg.Store(payload.Verbose)
|
||||||
closeSetup = f
|
closeSetup = f
|
||||||
}
|
}
|
||||||
|
|
||||||
if payload.Bwrap == nil {
|
if payload.Bwrap == nil {
|
||||||
fmsg.Fatal("bwrap config not supplied")
|
log.Fatal("bwrap config not supplied")
|
||||||
}
|
}
|
||||||
|
|
||||||
// restore bwrap sync fd
|
// restore bwrap sync fd
|
||||||
@ -65,7 +64,7 @@ func Main() {
|
|||||||
|
|
||||||
// close setup socket
|
// close setup socket
|
||||||
if err := closeSetup(); err != nil {
|
if err := closeSetup(); err != nil {
|
||||||
fmsg.Println("cannot close setup pipe:", err)
|
log.Println("cannot close setup pipe:", err)
|
||||||
// not fatal
|
// not fatal
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,15 +72,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 {
|
||||||
fmsg.Fatalf("cannot create home directory: %v", err)
|
log.Fatalf("cannot create home directory: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmsg.Fatalf("cannot access home directory: %v", err)
|
log.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() {
|
||||||
fmsg.Fatalf("data path %q is not a directory", payload.Home)
|
log.Fatalf("data path %q is not a directory", payload.Home)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ic init0.Payload
|
var ic init0.Payload
|
||||||
@ -95,10 +94,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 {
|
||||||
fmsg.Fatal("no command was specified and environment is unset")
|
log.Fatal("no command was specified and environment is unset")
|
||||||
}
|
}
|
||||||
if ic.Argv0, ok = payload.Bwrap.SetEnv["SHELL"]; !ok {
|
if ic.Argv0, ok = payload.Bwrap.SetEnv["SHELL"]; !ok {
|
||||||
fmsg.Fatal("no command was specified and $SHELL was unset")
|
log.Fatal("no command was specified and $SHELL was unset")
|
||||||
}
|
}
|
||||||
|
|
||||||
ic.Argv = []string{ic.Argv0}
|
ic.Argv = []string{ic.Argv0}
|
||||||
@ -110,20 +109,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 {
|
||||||
fmsg.Fatalf("cannot pipe: %v", err)
|
log.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.VPrintln("transmitting config to init")
|
fmsg.Verbose("transmitting config to init")
|
||||||
if err = encoder.Encode(&ic); err != nil {
|
if err = encoder.Encode(&ic); err != nil {
|
||||||
fmsg.Fatalf("cannot transmit init config: %v", err)
|
log.Fatalf("cannot transmit init config: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent
|
helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent
|
||||||
if fmsg.Verbose() {
|
if fmsg.Load() {
|
||||||
seccomp.CPrintln = fmsg.Println
|
seccomp.CPrintln = log.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"),
|
||||||
@ -131,7 +130,7 @@ func Main() {
|
|||||||
extraFiles,
|
extraFiles,
|
||||||
syncFd,
|
syncFd,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
fmsg.Fatalf("malformed sandbox config: %v", err)
|
log.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)
|
||||||
@ -139,15 +138,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 {
|
||||||
fmsg.Fatalf("cannot start target process: %v", err)
|
log.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) {
|
||||||
fmsg.Println("wait:", err)
|
log.Printf("wait: %v", err)
|
||||||
fmsg.Exit(127)
|
internal.Exit(127)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
fmsg.Exit(exitError.ExitCode())
|
internal.Exit(exitError.ExitCode())
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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 {
|
||||||
fmsg.Fatal("invalid fsu path, this copy of fortify is not compiled correctly")
|
return nil, fmsg.WrapError(errors.New("bad fsu path"),
|
||||||
panic("unreachable")
|
"invalid fsu path, this copy of fortify is not compiled correctly")
|
||||||
} 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.VPrintf("attaching supplementary group ids %s", supp)
|
fmsg.Verbosef("attaching supplementary group ids %s", supp)
|
||||||
s.cmd.Env = append(s.cmd.Env, "FORTIFY_GROUPS="+strings.Join(supp, " "))
|
s.cmd.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.VPrintln("starting shim via fsu:", s.cmd)
|
fmsg.Verbose("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 {
|
@ -4,14 +4,15 @@ 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"
|
||||||
)
|
)
|
||||||
@ -81,7 +82,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.Verbose(),
|
Verbose: fmsg.Load(),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -119,8 +120,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.Verbose() {
|
if fmsg.Load() {
|
||||||
fmsg.VPrintf("process %d exited with exit code %d", a.shim.Unwrap().Process.Pid, rs.ExitCode)
|
fmsg.Verbosef("process %d exited with exit code %d", a.shim.Unwrap().Process.Pid, rs.ExitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is reached when a fault makes an already running shim impossible to continue execution
|
// this is reached when a fault makes an already running shim impossible to continue execution
|
||||||
@ -128,11 +129,11 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
|
|||||||
// the effects of this is similar to the alternative exit path and ensures shim death
|
// 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
|
||||||
fmsg.Printf("cannot terminate shim on faulted setup: %v", err)
|
log.Printf("cannot terminate shim on faulted setup: %v", err)
|
||||||
|
|
||||||
// alternative exit path relying on shim behaviour on monitor process exit
|
// alternative exit path relying on shim behaviour on monitor process exit
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
fmsg.VPrintln("alternative exit path selected")
|
fmsg.Verbose("alternative exit path selected")
|
||||||
}
|
}
|
||||||
|
|
||||||
// child process exited, resume output
|
// child process exited, resume output
|
||||||
@ -163,10 +164,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.VPrintln("no other launchers active, will clean up globals")
|
fmsg.Verbose("no other launchers active, will clean up globals")
|
||||||
ec.Set(system.User)
|
ec.Set(system.User)
|
||||||
} else {
|
} else {
|
||||||
fmsg.VPrintf("found %d active launchers, cleaning up without globals", l)
|
fmsg.Verbosef("found %d active launchers, cleaning up without globals", l)
|
||||||
}
|
}
|
||||||
|
|
||||||
// accumulate capabilities of other launchers
|
// accumulate capabilities of other launchers
|
||||||
@ -174,7 +175,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 {
|
||||||
fmsg.Printf("state entry %d does not contain config", i)
|
log.Printf("state entry %d does not contain config", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,7 +185,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
|
|||||||
ec.Set(i)
|
ec.Set(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if fmsg.Verbose() {
|
if fmsg.Load() {
|
||||||
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) {
|
||||||
@ -192,7 +193,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(labels) > 0 {
|
if len(labels) > 0 {
|
||||||
fmsg.VPrintln("reverting operations labelled", strings.Join(labels, ", "))
|
fmsg.Verbose("reverting operations labelled", strings.Join(labels, ", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package proc
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -14,7 +15,8 @@ var (
|
|||||||
|
|
||||||
func copyExecutable() {
|
func copyExecutable() {
|
||||||
if name, err := os.Executable(); err != nil {
|
if name, err := os.Executable(); err != nil {
|
||||||
fmsg.Fatalf("cannot read executable path: %v", err)
|
fmsg.BeforeExit()
|
||||||
|
log.Fatalf("cannot read executable path: %v", err)
|
||||||
} else {
|
} else {
|
||||||
executable = name
|
executable = name
|
||||||
}
|
}
|
9
internal/exit.go
Normal file
9
internal/exit.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Exit(code int) { fmsg.BeforeExit(); os.Exit(code) }
|
@ -1,98 +0,0 @@
|
|||||||
package fmsg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
wstate atomic.Bool
|
|
||||||
dropped atomic.Uint64
|
|
||||||
withhold = make(chan struct{}, 1)
|
|
||||||
msgbuf = make(chan dOp, 64) // these ops are tiny so a large buffer is allocated for withholding output
|
|
||||||
|
|
||||||
dequeueOnce sync.Once
|
|
||||||
queueSync sync.WaitGroup
|
|
||||||
)
|
|
||||||
|
|
||||||
func dequeue() {
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case op := <-msgbuf:
|
|
||||||
op.Do()
|
|
||||||
queueSync.Done()
|
|
||||||
case <-withhold:
|
|
||||||
<-withhold
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// queue submits ops to msgbuf but drops messages
|
|
||||||
// when the buffer is full and dequeue is withholding
|
|
||||||
func queue(op dOp) {
|
|
||||||
queueSync.Add(1)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case msgbuf <- op:
|
|
||||||
default:
|
|
||||||
// send the op anyway if not withholding
|
|
||||||
// as dequeue will get to it eventually
|
|
||||||
if !wstate.Load() {
|
|
||||||
msgbuf <- op
|
|
||||||
} else {
|
|
||||||
queueSync.Done()
|
|
||||||
// increment dropped message count
|
|
||||||
dropped.Add(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type dOp interface{ Do() }
|
|
||||||
|
|
||||||
func Exit(code int) {
|
|
||||||
Resume() // resume here to avoid deadlock
|
|
||||||
queueSync.Wait()
|
|
||||||
os.Exit(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Suspend() {
|
|
||||||
dequeueOnce.Do(dequeue)
|
|
||||||
if wstate.CompareAndSwap(false, true) {
|
|
||||||
queueSync.Wait()
|
|
||||||
withhold <- struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Resume() {
|
|
||||||
dequeueOnce.Do(dequeue)
|
|
||||||
if wstate.CompareAndSwap(true, false) {
|
|
||||||
withhold <- struct{}{}
|
|
||||||
if d := dropped.Swap(0); d != 0 {
|
|
||||||
Printf("dropped %d messages during withhold", d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type dPrint []any
|
|
||||||
|
|
||||||
func (v dPrint) Do() {
|
|
||||||
std.Print(v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
type dPrintf struct {
|
|
||||||
format string
|
|
||||||
v []any
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dPrintf) Do() {
|
|
||||||
std.Printf(d.format, d.v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
type dPrintln []any
|
|
||||||
|
|
||||||
func (v dPrintln) Do() {
|
|
||||||
std.Println(v...)
|
|
||||||
}
|
|
@ -2,39 +2,85 @@
|
|||||||
package fmsg
|
package fmsg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
var std = log.New(os.Stderr, "fortify: ", 0)
|
const (
|
||||||
|
bufSize = 4 * 1024
|
||||||
|
bufSizeMax = 16 * 1024 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
func SetPrefix(prefix string) {
|
var o = &suspendable{w: os.Stderr}
|
||||||
prefix += ": "
|
|
||||||
std.SetPrefix(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) }
|
||||||
|
|
||||||
|
type suspendable struct {
|
||||||
|
w io.Writer
|
||||||
|
s atomic.Bool
|
||||||
|
|
||||||
|
buf bytes.Buffer
|
||||||
|
bufOnce sync.Once
|
||||||
|
bufMu sync.Mutex
|
||||||
|
dropped int
|
||||||
}
|
}
|
||||||
|
|
||||||
func Print(v ...any) {
|
func (s *suspendable) Write(p []byte) (n int, err error) {
|
||||||
dequeueOnce.Do(dequeue)
|
if !s.s.Load() {
|
||||||
queue(dPrint(v))
|
return s.w.Write(p)
|
||||||
|
}
|
||||||
|
s.bufOnce.Do(func() { s.prepareBuf() })
|
||||||
|
|
||||||
|
s.bufMu.Lock()
|
||||||
|
defer s.bufMu.Unlock()
|
||||||
|
|
||||||
|
if l := len(p); s.buf.Len()+l > bufSizeMax {
|
||||||
|
s.dropped += l
|
||||||
|
return 0, syscall.ENOMEM
|
||||||
|
}
|
||||||
|
return s.buf.Write(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Printf(format string, v ...any) {
|
func (s *suspendable) prepareBuf() { s.buf.Grow(bufSize) }
|
||||||
dequeueOnce.Do(dequeue)
|
func (s *suspendable) Suspend() bool { return o.s.CompareAndSwap(false, true) }
|
||||||
queue(&dPrintf{format, v})
|
func (s *suspendable) Resume() (resumed bool, dropped uintptr, n int64, err error) {
|
||||||
|
if o.s.CompareAndSwap(true, false) {
|
||||||
|
o.bufMu.Lock()
|
||||||
|
defer o.bufMu.Unlock()
|
||||||
|
|
||||||
|
resumed = true
|
||||||
|
dropped = uintptr(o.dropped)
|
||||||
|
|
||||||
|
o.dropped = 0
|
||||||
|
n, err = io.Copy(s.w, &s.buf)
|
||||||
|
s.buf = bytes.Buffer{}
|
||||||
|
s.prepareBuf()
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func Println(v ...any) {
|
func Suspend() bool { return o.Suspend() }
|
||||||
dequeueOnce.Do(dequeue)
|
func Resume() bool {
|
||||||
queue(dPrintln(v))
|
resumed, dropped, _, err := o.Resume()
|
||||||
|
if err != nil {
|
||||||
|
// probably going to result in an error as well,
|
||||||
|
// so this call is as good as unreachable
|
||||||
|
log.Printf("cannot dump buffer on resume: %v", err)
|
||||||
|
}
|
||||||
|
if resumed && dropped > 0 {
|
||||||
|
log.Fatalf("dropped %d bytes while output is suspended", dropped)
|
||||||
|
}
|
||||||
|
return resumed
|
||||||
}
|
}
|
||||||
|
|
||||||
func Fatal(v ...any) {
|
func BeforeExit() {
|
||||||
Print(v...)
|
if Resume() {
|
||||||
Exit(1)
|
log.Printf("beforeExit reached on suspended output")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Fatalf(format string, v ...any) {
|
|
||||||
Printf(format, v...)
|
|
||||||
Exit(1)
|
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,23 @@
|
|||||||
package fmsg
|
package fmsg
|
||||||
|
|
||||||
import "sync/atomic"
|
import (
|
||||||
|
"log"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
var verbose = new(atomic.Bool)
|
var verbose = new(atomic.Bool)
|
||||||
|
|
||||||
func Verbose() bool {
|
func Load() bool { return verbose.Load() }
|
||||||
return verbose.Load()
|
func Store(v bool) { verbose.Store(v) }
|
||||||
}
|
|
||||||
|
|
||||||
func SetVerbose(v bool) {
|
func Verbosef(format string, v ...any) {
|
||||||
verbose.Store(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func VPrintf(format string, v ...any) {
|
|
||||||
if verbose.Load() {
|
if verbose.Load() {
|
||||||
Printf(format, v...)
|
log.Printf(format, v...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func VPrintln(v ...any) {
|
func Verbose(v ...any) {
|
||||||
if verbose.Load() {
|
if verbose.Load() {
|
||||||
Println(v...)
|
log.Println(v...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.VPrintf("process share directory at %q", v.SharePath)
|
fmsg.Verbosef("process share directory at %q", v.SharePath)
|
||||||
|
|
||||||
if r, ok := os.LookupEnv(xdgRuntimeDir); !ok || r == "" || !path.IsAbs(r) {
|
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.VPrintf("runtime directory at %q", v.RunDirPath)
|
fmsg.Verbosef("runtime directory at %q", v.RunDirPath)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package linux
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
@ -11,7 +12,6 @@ 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 proc.MustExecutable() }
|
func (s *Std) MustExecutable() string { return internal.MustExecutable() }
|
||||||
func (s *Std) LookupGroup(name string) (*user.Group, error) { return user.LookupGroup(name) }
|
func (s *Std) 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) { fmsg.Exit(code) }
|
func (s *Std) Exit(code int) { internal.Exit(code) }
|
||||||
|
|
||||||
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
||||||
|
|
||||||
@ -74,8 +74,10 @@ 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.Fatal("invalid fsu path, this copy of fortify is not compiled correctly")
|
fmsg.BeforeExit()
|
||||||
panic("unreachable")
|
log.Fatal("invalid fsu path, this copy of fortify is not compiled correctly")
|
||||||
|
// unreachable
|
||||||
|
return 0, syscall.EBADE
|
||||||
} else {
|
} else {
|
||||||
cmd := exec.Command(fsu)
|
cmd := exec.Command(fsu)
|
||||||
cmd.Path = fsu
|
cmd.Path = fsu
|
||||||
|
@ -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.VPrintf("skipped non-directory entry %q", e.Name())
|
fmsg.Verbosef("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.VPrintf("skipped non-aid entry %q", e.Name())
|
fmsg.Verbosef("skipped non-aid entry %q", e.Name())
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
if v < 0 || v > 9999 {
|
if v < 0 || v > 9999 {
|
||||||
fmsg.VPrintf("skipped out of bounds entry %q", e.Name())
|
fmsg.Verbosef("skipped out of bounds entry %q", e.Name())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,18 +36,18 @@ func (a *ACL) Type() Enablement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACL) apply(sys *I) error {
|
func (a *ACL) apply(sys *I) error {
|
||||||
fmsg.VPrintln("applying ACL", a)
|
fmsg.Verbose("applying ACL", a)
|
||||||
return fmsg.WrapErrorSuffix(acl.UpdatePerm(a.path, sys.uid, a.perms...),
|
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.VPrintln("stripping ACL", a)
|
fmsg.Verbose("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.VPrintln("skipping ACL", a)
|
fmsg.Verbose("skipping ACL", a)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package system
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -47,12 +48,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.Verbose() && d.proxy.Sealed() {
|
if fmsg.Load() && d.proxy.Sealed() {
|
||||||
fmsg.VPrintln("sealed session proxy", session.Args(sessionBus))
|
fmsg.Verbose("sealed session proxy", session.Args(sessionBus))
|
||||||
if system != nil {
|
if system != nil {
|
||||||
fmsg.VPrintln("sealed system proxy", system.Args(systemBus))
|
fmsg.Verbose("sealed system proxy", system.Args(systemBus))
|
||||||
}
|
}
|
||||||
fmsg.VPrintln("message bus proxy final args:", d.proxy)
|
fmsg.Verbose("message bus proxy final args:", d.proxy)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -78,9 +79,9 @@ func (d *DBus) Type() Enablement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DBus) apply(sys *I) error {
|
func (d *DBus) apply(sys *I) error {
|
||||||
fmsg.VPrintf("session bus proxy on %q for upstream %q", d.proxy.Session()[1], d.proxy.Session()[0])
|
fmsg.Verbosef("session bus proxy on %q for upstream %q", d.proxy.Session()[1], d.proxy.Session()[0])
|
||||||
if d.system {
|
if d.system {
|
||||||
fmsg.VPrintf("system bus proxy on %q for upstream %q", d.proxy.System()[1], d.proxy.System()[0])
|
fmsg.Verbosef("system bus proxy on %q for upstream %q", d.proxy.System()[1], d.proxy.System()[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// this starts the process and blocks until ready
|
// this starts the process and blocks until ready
|
||||||
@ -89,15 +90,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.VPrintln("starting message bus proxy:", d.proxy)
|
fmsg.Verbose("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.VPrintln("terminating message bus proxy")
|
fmsg.Verbose("terminating message bus proxy")
|
||||||
d.proxy.Close()
|
d.proxy.Close()
|
||||||
defer fmsg.VPrintln("message bus proxy exit")
|
defer fmsg.Verbose("message bus proxy exit")
|
||||||
return fmsg.WrapErrorSuffix(d.proxy.Wait(), "message bus proxy error:")
|
return fmsg.WrapErrorSuffix(d.proxy.Wait(), "message bus proxy error:")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +145,7 @@ func (s *scanToFmsg) write(p []byte, a int) (int, error) {
|
|||||||
func (s *scanToFmsg) Dump() {
|
func (s *scanToFmsg) Dump() {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
for _, msg := range s.msgbuf {
|
for _, msg := range s.msgbuf {
|
||||||
fmsg.Println(msg)
|
log.Println(msg)
|
||||||
}
|
}
|
||||||
s.mu.RUnlock()
|
s.mu.RUnlock()
|
||||||
}
|
}
|
||||||
|
53
internal/system/link.go
Normal file
53
internal/system/link.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Link registers an Op that links dst to src.
|
||||||
|
func (sys *I) Link(oldname, newname string) *I { return sys.LinkFileType(Process, oldname, newname) }
|
||||||
|
|
||||||
|
// LinkFileType registers a file linking Op labelled with type et.
|
||||||
|
func (sys *I) LinkFileType(et Enablement, oldname, newname string) *I {
|
||||||
|
sys.lock.Lock()
|
||||||
|
defer sys.lock.Unlock()
|
||||||
|
|
||||||
|
sys.ops = append(sys.ops, &Hardlink{et, newname, oldname})
|
||||||
|
|
||||||
|
return sys
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hardlink struct {
|
||||||
|
et Enablement
|
||||||
|
dst, src string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Hardlink) Type() Enablement { return l.et }
|
||||||
|
|
||||||
|
func (l *Hardlink) apply(_ *I) error {
|
||||||
|
fmsg.Verbose("linking", l)
|
||||||
|
return fmsg.WrapErrorSuffix(os.Link(l.src, l.dst),
|
||||||
|
fmt.Sprintf("cannot link %q:", l.dst))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Hardlink) revert(_ *I, ec *Criteria) error {
|
||||||
|
if ec.hasType(l) {
|
||||||
|
fmsg.Verbosef("removing hard link %q", l.dst)
|
||||||
|
return fmsg.WrapErrorSuffix(os.Remove(l.dst),
|
||||||
|
fmt.Sprintf("cannot remove hard link %q:", l.dst))
|
||||||
|
} else {
|
||||||
|
fmsg.Verbosef("skipping hard link %q", l.dst)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Hardlink) Is(o Op) bool {
|
||||||
|
l0, ok := o.(*Hardlink)
|
||||||
|
return ok && l0 != nil && *l == *l0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Hardlink) Path() string { return l.src }
|
||||||
|
func (l *Hardlink) String() string { return fmt.Sprintf("%q from %q", l.dst, l.src) }
|
@ -40,7 +40,7 @@ func (m *Mkdir) Type() Enablement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mkdir) apply(_ *I) error {
|
func (m *Mkdir) apply(_ *I) error {
|
||||||
fmsg.VPrintln("ensuring directory", m)
|
fmsg.Verbose("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.VPrintln("destroying ephemeral directory", m)
|
fmsg.Verbose("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.VPrintln("skipping ephemeral directory", m)
|
fmsg.Verbose("skipping ephemeral directory", m)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package system
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -105,9 +106,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.VPrintf("commit faulted after %d ops, rolling back partial commit", len(sp.ops))
|
fmsg.Verbosef("commit faulted after %d ops, rolling back partial commit", len(sp.ops))
|
||||||
if err := sp.Revert(&Criteria{nil}); err != nil {
|
if err := sp.Revert(&Criteria{nil}); err != nil {
|
||||||
fmsg.Println("errors returned reverting partial commit:", err)
|
log.Println("errors returned reverting partial commit:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -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("/tmp/fortify.1971/30c9543e0a2c9621a8bfecb9d874c347/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie"),
|
CopyFile(new([]byte), "/home/ophestra/xdg/config/pulse/cookie", 0, 256),
|
||||||
system.New(150).
|
system.New(150).
|
||||||
ChangeHosts("chronos").
|
ChangeHosts("chronos").
|
||||||
Ensure("/run", 0755),
|
Ensure("/run", 0755),
|
||||||
|
@ -1,117 +1,72 @@
|
|||||||
package system
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"syscall"
|
||||||
|
|
||||||
"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 path dst from src.
|
// CopyFile registers an Op that copies from src.
|
||||||
func (sys *I) CopyFile(dst, src string) *I {
|
// A buffer is initialised with size cap and the Op faults if bytes read exceed n.
|
||||||
return sys.CopyFileType(Process, dst, src)
|
func (sys *I) CopyFile(payload *[]byte, src string, cap int, n int64) *I {
|
||||||
}
|
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{et, tmpfileCopy, dst, src})
|
sys.ops = append(sys.ops, &Tmpfile{payload, src, n, buf})
|
||||||
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 {
|
||||||
et Enablement
|
payload *[]byte
|
||||||
method uint8
|
src string
|
||||||
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 {
|
||||||
switch t.method {
|
fmsg.Verbose("copying", t)
|
||||||
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 b, err := os.Stat(t.src); err != nil {
|
||||||
if ec.hasType(t) {
|
return fmsg.WrapErrorSuffix(err,
|
||||||
fmsg.VPrintf("removing tmpfile %q", t.dst)
|
fmt.Sprintf("cannot stat %q:", t.src))
|
||||||
return fmsg.WrapErrorSuffix(os.Remove(t.dst),
|
|
||||||
fmt.Sprintf("cannot remove tmpfile %q:", t.dst))
|
|
||||||
} else {
|
} else {
|
||||||
fmsg.VPrintf("skipping tmpfile %q", t.dst)
|
if b.IsDir() {
|
||||||
return nil
|
return fmsg.WrapErrorSuffix(syscall.EISDIR,
|
||||||
|
fmt.Sprintf("%q is a directory", t.src))
|
||||||
|
}
|
||||||
|
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 {
|
func (t *Tmpfile) Is(o Op) bool {
|
||||||
t0, ok := o.(*Tmpfile)
|
t0, ok := o.(*Tmpfile)
|
||||||
return ok && t0 != nil && *t == *t0
|
return ok && t0 != nil &&
|
||||||
}
|
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) }
|
||||||
|
@ -1,46 +1,25 @@
|
|||||||
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 {
|
|
||||||
dst, src string
|
|
||||||
}{
|
|
||||||
{"/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.dst+" from "+tc.src, func(t *testing.T) {
|
|
||||||
sys := New(150)
|
|
||||||
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 {
|
testCases := []struct {
|
||||||
tcOp
|
tcOp
|
||||||
dst string
|
cap int
|
||||||
|
n int64
|
||||||
}{
|
}{
|
||||||
{tcOp{User, "/tmp/fortify.1971/f587afe9fce3c8e1ad5b64deb6c41ad5/pulse-cookie"}, "/home/ophestra/xdg/config/pulse/cookie"},
|
{tcOp{Process, "/home/ophestra/xdg/config/pulse/cookie"}, 256, 256},
|
||||||
{tcOp{Process, "/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.dst+" from "+tc.path+" with type "+TypeString(tc.et), func(t *testing.T) {
|
t.Run("copy file "+tc.path+" with cap = "+strconv.Itoa(tc.cap)+" n = "+strconv.Itoa(int(tc.n)), func(t *testing.T) {
|
||||||
sys := New(150)
|
sys := New(150)
|
||||||
sys.CopyFileType(tc.et, tc.dst, tc.path)
|
sys.CopyFile(new([]byte), tc.path, tc.cap, tc.n)
|
||||||
tc.test(t, sys.ops, []Op{
|
tc.test(t, sys.ops, []Op{
|
||||||
&Tmpfile{tc.et, tmpfileCopy, tc.dst, tc.path},
|
&Tmpfile{nil, tc.path, tc.n, nil},
|
||||||
&ACL{tc.et, tc.dst, []acl.Perm{acl.Read}},
|
}, "CopyFile")
|
||||||
}, "CopyFileType")
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,7 +36,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{
|
||||||
&Tmpfile{Process, tmpfileLink, tc.dst, tc.src},
|
&Hardlink{Process, tc.dst, tc.src},
|
||||||
}, "Link")
|
}, "Link")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -76,44 +55,25 @@ 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{
|
||||||
&Tmpfile{tc.et, tmpfileLink, tc.dst, tc.path},
|
&Hardlink{tc.et, 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 {
|
||||||
method uint8
|
src string
|
||||||
dst, src string
|
n int64
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{tmpfileCopy, "/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie",
|
{"/home/ophestra/xdg/config/pulse/cookie", 256,
|
||||||
`"/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse-cookie" from "/home/ophestra/xdg/config/pulse/cookie"`},
|
`up to 256 bytes 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{
|
if got := (&Tmpfile{src: tc.src, n: tc.n}).String(); got != tc.want {
|
||||||
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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -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.VPrintf("wayland attached on %q", w.pair[1])
|
fmsg.Verbosef("wayland attached on %q", w.pair[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
if sp, err := w.conn.Bind(w.pair[0], w.appID, w.instanceID); err != nil {
|
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.VPrintf("wayland listening on %q", w.pair[0])
|
fmsg.Verbosef("wayland listening on %q", w.pair[0])
|
||||||
return fmsg.WrapErrorSuffix(errors.Join(os.Chmod(w.pair[0], 0), acl.UpdatePerm(w.pair[0], sys.uid, acl.Read, acl.Write, acl.Execute)),
|
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.VPrintf("removing wayland socket on %q", w.pair[0])
|
fmsg.Verbosef("removing wayland socket on %q", w.pair[0])
|
||||||
if err := os.Remove(w.pair[0]); err != nil && !errors.Is(err, os.ErrNotExist) {
|
if err := os.Remove(w.pair[0]); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmsg.VPrintf("detaching from wayland on %q", w.pair[1])
|
fmsg.Verbosef("detaching from wayland on %q", w.pair[1])
|
||||||
return fmsg.WrapErrorSuffix(w.conn.Close(),
|
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.VPrintf("skipping wayland cleanup on %q", w.pair[0])
|
fmsg.Verbosef("skipping wayland cleanup on %q", w.pair[0])
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,18 +24,18 @@ func (x XHost) Type() Enablement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (x XHost) apply(_ *I) error {
|
func (x XHost) apply(_ *I) error {
|
||||||
fmsg.VPrintf("inserting entry %s to X11", x)
|
fmsg.Verbosef("inserting entry %s to X11", x)
|
||||||
return fmsg.WrapErrorSuffix(xcb.ChangeHosts(xcb.HostModeInsert, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)),
|
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.VPrintf("deleting entry %s from X11", x)
|
fmsg.Verbosef("deleting entry %s from X11", x)
|
||||||
return fmsg.WrapErrorSuffix(xcb.ChangeHosts(xcb.HostModeDelete, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)),
|
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.VPrintf("skipping entry %s in X11", x)
|
fmsg.Verbosef("skipping entry %s in X11", x)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
75
main.go
75
main.go
@ -5,6 +5,7 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"os/user"
|
"os/user"
|
||||||
@ -20,10 +21,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"
|
||||||
)
|
)
|
||||||
@ -37,6 +38,8 @@ 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")
|
||||||
}
|
}
|
||||||
@ -62,13 +65,12 @@ 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 {
|
||||||
fmsg.Printf("cannot set SUID_DUMP_DISABLE: %s", err)
|
log.Printf("cannot set SUID_DUMP_DISABLE: %s", err)
|
||||||
// not fatal: this program runs as the privileged user
|
// not fatal: this program runs as the privileged user
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.Geteuid() == 0 {
|
if os.Geteuid() == 0 {
|
||||||
fmsg.Fatal("this program must not run as root")
|
log.Fatal("this program must not run as root")
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
flag.CommandLine.Usage = func() {
|
flag.CommandLine.Usage = func() {
|
||||||
@ -96,12 +98,12 @@ func main() {
|
|||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
fmsg.SetVerbose(flagVerbose)
|
fmsg.Store(flagVerbose)
|
||||||
|
|
||||||
args := flag.Args()
|
args := flag.Args()
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
flag.CommandLine.Usage()
|
flag.CommandLine.Usage()
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
@ -111,16 +113,20 @@ func main() {
|
|||||||
} else {
|
} else {
|
||||||
fmt.Println("impure")
|
fmt.Println("impure")
|
||||||
}
|
}
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
|
|
||||||
case "license": // print embedded license
|
case "license": // print embedded license
|
||||||
fmt.Println(license)
|
fmt.Println(license)
|
||||||
fmsg.Exit(0)
|
internal.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())
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
|
|
||||||
case "help": // print help message
|
case "help": // print help message
|
||||||
flag.CommandLine.Usage()
|
flag.CommandLine.Usage()
|
||||||
fmsg.Exit(0)
|
internal.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
|
||||||
@ -130,7 +136,8 @@ 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)
|
||||||
fmsg.Exit(0)
|
internal.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
|
||||||
@ -142,6 +149,7 @@ 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)
|
||||||
@ -149,14 +157,15 @@ 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:
|
|
||||||
fmsg.Fatal("show requires 1 argument")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmsg.Exit(0)
|
default:
|
||||||
|
log.Fatal("show requires 1 argument")
|
||||||
|
}
|
||||||
|
internal.Exit(0)
|
||||||
|
|
||||||
case "app": // launch app from configuration file
|
case "app": // launch app from configuration file
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
fmsg.Fatal("app requires at least 1 argument")
|
log.Fatal("app requires at least 1 argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
// config extraArgs...
|
// config extraArgs...
|
||||||
@ -166,6 +175,7 @@ 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)
|
||||||
|
|
||||||
@ -208,8 +218,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if aid < 0 || aid > 9999 {
|
if aid < 0 || aid > 9999 {
|
||||||
fmsg.Fatalf("aid %d out of range", aid)
|
log.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
|
||||||
@ -219,13 +228,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 {
|
||||||
fmsg.Fatalf("cannot obtain uid from fsu: %v", err)
|
log.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.VPrintf("cannot look up uid %s", us)
|
fmsg.Verbosef("cannot look up uid %s", us)
|
||||||
passwd = &user.User{
|
passwd = &user.User{
|
||||||
Uid: us,
|
Uid: us,
|
||||||
Gid: us,
|
Gid: us,
|
||||||
@ -267,7 +276,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 {
|
||||||
fmsg.Fatalf("cannot load session bus proxy config from %q: %s", dbusConfigSession, err)
|
log.Fatalf("cannot load session bus proxy config from %q: %s", dbusConfigSession, err)
|
||||||
} else {
|
} else {
|
||||||
config.Confinement.SessionBus = c
|
config.Confinement.SessionBus = c
|
||||||
}
|
}
|
||||||
@ -276,7 +285,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 {
|
||||||
fmsg.Fatalf("cannot load system bus proxy config from %q: %s", dbusConfigSystem, err)
|
log.Fatalf("cannot load system bus proxy config from %q: %s", dbusConfigSystem, err)
|
||||||
} else {
|
} else {
|
||||||
config.Confinement.SystemBus = c
|
config.Confinement.SystemBus = c
|
||||||
}
|
}
|
||||||
@ -291,17 +300,18 @@ func main() {
|
|||||||
|
|
||||||
// invoke app
|
// invoke app
|
||||||
runApp(config)
|
runApp(config)
|
||||||
|
panic("unreachable")
|
||||||
|
|
||||||
// internal commands
|
// internal commands
|
||||||
case "shim":
|
case "shim":
|
||||||
shim.Main()
|
shim.Main()
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
case "init":
|
case "init":
|
||||||
init0.Main()
|
init0.Main()
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
fmsg.Fatalf("%q is not a valid command", args[0])
|
log.Fatalf("%q is not a valid command", args[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
@ -313,15 +323,15 @@ func runApp(config *fst.Config) {
|
|||||||
syscall.SIGINT, syscall.SIGTERM)
|
syscall.SIGINT, syscall.SIGTERM)
|
||||||
defer stop() // unreachable
|
defer stop() // unreachable
|
||||||
|
|
||||||
if fmsg.Verbose() {
|
if fmsg.Load() {
|
||||||
seccomp.CPrintln = fmsg.Println
|
seccomp.CPrintln = log.Println
|
||||||
}
|
}
|
||||||
|
|
||||||
if a, err := app.New(sys); err != nil {
|
if a, err := app.New(sys); err != nil {
|
||||||
fmsg.Fatalf("cannot create app: %s\n", err)
|
log.Fatalf("cannot create app: %s", err)
|
||||||
} else if err = a.Seal(config); err != nil {
|
} else if err = a.Seal(config); err != nil {
|
||||||
logBaseError(err, "cannot seal app:")
|
logBaseError(err, "cannot seal app:")
|
||||||
fmsg.Exit(1)
|
internal.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:")
|
||||||
@ -334,8 +344,7 @@ func runApp(config *fst.Config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rs.WaitErr != nil {
|
if rs.WaitErr != nil {
|
||||||
fmsg.Println("inner wait failed:", rs.WaitErr)
|
log.Println("inner wait failed:", rs.WaitErr)
|
||||||
}
|
}
|
||||||
fmsg.Exit(rs.ExitCode)
|
internal.Exit(rs.ExitCode)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ package
|
|||||||
|
|
||||||
|
|
||||||
*Default:*
|
*Default:*
|
||||||
` <derivation fortify-0.2.14> `
|
` <derivation fortify-0.2.15> `
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
buildGoModule rec {
|
buildGoModule rec {
|
||||||
pname = "fortify";
|
pname = "fortify";
|
||||||
version = "0.2.14";
|
version = "0.2.15";
|
||||||
|
|
||||||
src = builtins.path {
|
src = builtins.path {
|
||||||
name = "fortify-src";
|
name = "fortify-src";
|
||||||
|
21
parse.go
21
parse.go
@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -21,11 +22,10 @@ func tryPath(name string) (config *fst.Config) {
|
|||||||
if name != "-" {
|
if name != "-" {
|
||||||
r = tryFd(name)
|
r = tryFd(name)
|
||||||
if r == nil {
|
if r == nil {
|
||||||
fmsg.VPrintln("load configuration from file")
|
fmsg.Verbose("load configuration from file")
|
||||||
|
|
||||||
if f, err := os.Open(name); err != nil {
|
if f, err := os.Open(name); err != nil {
|
||||||
fmsg.Fatalf("cannot access configuration file %q: %s", name, err)
|
log.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 {
|
||||||
fmsg.Printf("cannot close config fd: %v", err)
|
log.Printf("cannot close config fd: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@ -42,8 +42,7 @@ func tryPath(name string) (config *fst.Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := json.NewDecoder(r).Decode(&config); err != nil {
|
if err := json.NewDecoder(r).Decode(&config); err != nil {
|
||||||
fmsg.Fatalf("cannot load configuration: %v", err)
|
log.Fatalf("cannot load configuration: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -51,7 +50,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.VPrintf("name cannot be interpreted as int64: %v", err)
|
fmsg.Verbosef("name cannot be interpreted as int64: %v", err)
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
fd := uintptr(v)
|
fd := uintptr(v)
|
||||||
@ -59,7 +58,7 @@ func tryFd(name string) io.ReadCloser {
|
|||||||
if errors.Is(errno, syscall.EBADF) {
|
if errors.Is(errno, syscall.EBADF) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
fmsg.Fatalf("cannot get fd %d: %v", fd, errno)
|
log.Fatalf("cannot get fd %d: %v", fd, errno)
|
||||||
}
|
}
|
||||||
return os.NewFile(fd, strconv.Itoa(v))
|
return os.NewFile(fd, strconv.Itoa(v))
|
||||||
}
|
}
|
||||||
@ -83,11 +82,11 @@ func tryShort(name string) (config *fst.Config, instance *state.State) {
|
|||||||
|
|
||||||
// try to match from state store
|
// try to match from state store
|
||||||
if likePrefix && len(name) >= 8 {
|
if likePrefix && len(name) >= 8 {
|
||||||
fmsg.VPrintln("argument looks like prefix")
|
fmsg.Verbose("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 {
|
||||||
fmsg.Printf("cannot join store: %v", err)
|
log.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 {
|
||||||
@ -99,7 +98,7 @@ func tryShort(name string) (config *fst.Config, instance *state.State) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
fmsg.VPrintf("instance %s skipped", v)
|
fmsg.Verbosef("instance %s skipped", v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
30
print.go
30
print.go
@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -12,7 +13,6 @@ 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 {
|
||||||
fmsg.Fatalf("cannot obtain uid from fsu: %v", err)
|
log.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 {
|
||||||
fmsg.Fatalf("cannot join store: %v", err)
|
log.Fatalf("cannot join store: %v", err)
|
||||||
} else {
|
} else {
|
||||||
entries = e
|
entries = e
|
||||||
}
|
}
|
||||||
if err := s.Close(); err != nil {
|
if err := s.Close(); err != nil {
|
||||||
fmsg.Printf("cannot close store: %v", err)
|
log.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 {
|
||||||
fmsg.Printf("got invalid state entry %s", id.String())
|
log.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 {
|
||||||
fmsg.Printf("possible store corruption: entry %s has id %s",
|
log.Printf("possible store corruption: entry %s has id %s",
|
||||||
id.String(), instance.ID.String())
|
id.String(), instance.ID.String())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -273,8 +273,7 @@ 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 {
|
||||||
fmsg.Fatalf("cannot serialise: %v", err)
|
log.Fatalf("cannot serialise: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,31 +283,26 @@ 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 {
|
||||||
fmsg.Fatalf("cannot write to tabwriter: %v", err)
|
log.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 {
|
||||||
fmsg.Fatalf("cannot write to tabwriter: %v", err)
|
log.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 {
|
||||||
fmsg.Fatalf("cannot flush tabwriter: %v", err)
|
log.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 {
|
||||||
fmsg.Fatalf("cannot print: %v", err)
|
log.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 {
|
||||||
fmsg.Fatalf("cannot print: %v", err)
|
log.Fatalf("cannot print: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
test.nix
13
test.nix
@ -3,6 +3,7 @@
|
|||||||
self,
|
self,
|
||||||
home-manager,
|
home-manager,
|
||||||
nixosTest,
|
nixosTest,
|
||||||
|
fortify,
|
||||||
}:
|
}:
|
||||||
|
|
||||||
nixosTest {
|
nixosTest {
|
||||||
@ -110,6 +111,18 @@ 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;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user