aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/system
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-02-17 19:00:43 +0900
committerOphestra <cat@gensokyo.uk>2025-02-17 19:00:43 +0900
commit90cb01b2748550228d0bc179691a19ebb996fc36 (patch)
tree06a2e17b578312c3f0734bf4c476e3c2eb5f6908 /internal/system
parentb1e1d5627e6532ec719c761846d7f38fe93fcadd (diff)
system: move out of internal
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/system')
-rw-r--r--internal/system/acl.go65
-rw-r--r--internal/system/acl_test.go90
-rw-r--r--internal/system/dbus.go148
-rw-r--r--internal/system/enablement.go68
-rw-r--r--internal/system/link.go47
-rw-r--r--internal/system/mkdir.go86
-rw-r--r--internal/system/mkdir_test.go73
-rw-r--r--internal/system/op.go162
-rw-r--r--internal/system/op_internal_test.go79
-rw-r--r--internal/system/op_test.go129
-rw-r--r--internal/system/tmpfiles.go76
-rw-r--r--internal/system/tmpfiles_test.go81
-rw-r--r--internal/system/wayland.go89
-rw-r--r--internal/system/xhost.go53
-rw-r--r--internal/system/xhost_test.go34
15 files changed, 0 insertions, 1280 deletions
diff --git a/internal/system/acl.go b/internal/system/acl.go
deleted file mode 100644
index 840c3971..00000000
--- a/internal/system/acl.go
+++ /dev/null
@@ -1,65 +0,0 @@
-package system
-
-import (
- "fmt"
- "slices"
-
- "git.gensokyo.uk/security/fortify/acl"
-)
-
-// UpdatePerm appends an ephemeral acl update Op.
-func (sys *I) UpdatePerm(path string, perms ...acl.Perm) *I {
- sys.UpdatePermType(Process, path, perms...)
-
- return sys
-}
-
-// UpdatePermType appends an acl update Op.
-func (sys *I) UpdatePermType(et Enablement, path string, perms ...acl.Perm) *I {
- sys.lock.Lock()
- defer sys.lock.Unlock()
-
- sys.ops = append(sys.ops, &ACL{et, path, perms})
-
- return sys
-}
-
-type ACL struct {
- et Enablement
- path string
- perms acl.Perms
-}
-
-func (a *ACL) Type() Enablement { return a.et }
-
-func (a *ACL) apply(sys *I) error {
- sys.println("applying ACL", a)
- return sys.wrapErrSuffix(acl.UpdatePerm(a.path, sys.uid, a.perms...),
- fmt.Sprintf("cannot apply ACL entry to %q:", a.path))
-}
-
-func (a *ACL) revert(sys *I, ec *Criteria) error {
- if ec.hasType(a) {
- sys.println("stripping ACL", a)
- return sys.wrapErrSuffix(acl.UpdatePerm(a.path, sys.uid),
- fmt.Sprintf("cannot strip ACL entry from %q:", a.path))
- } else {
- sys.println("skipping ACL", a)
- return nil
- }
-}
-
-func (a *ACL) Is(o Op) bool {
- a0, ok := o.(*ACL)
- return ok && a0 != nil &&
- a.et == a0.et &&
- a.path == a0.path &&
- slices.Equal(a.perms, a0.perms)
-}
-
-func (a *ACL) Path() string { return a.path }
-
-func (a *ACL) String() string {
- return fmt.Sprintf("%s type: %s path: %q",
- a.perms, TypeString(a.et), a.path)
-}
diff --git a/internal/system/acl_test.go b/internal/system/acl_test.go
deleted file mode 100644
index ed154ad1..00000000
--- a/internal/system/acl_test.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package system
-
-import (
- "testing"
-
- "git.gensokyo.uk/security/fortify/acl"
-)
-
-func TestUpdatePerm(t *testing.T) {
- testCases := []struct {
- path string
- perms []acl.Perm
- }{
- {"/run/user/1971/fortify", []acl.Perm{acl.Execute}},
- {"/tmp/fortify.1971/tmpdir/150", []acl.Perm{acl.Read, acl.Write, acl.Execute}},
- }
-
- for _, tc := range testCases {
- t.Run(tc.path+permSubTestSuffix(tc.perms), func(t *testing.T) {
- sys := New(150)
- sys.UpdatePerm(tc.path, tc.perms...)
- (&tcOp{Process, tc.path}).test(t, sys.ops, []Op{&ACL{Process, tc.path, tc.perms}}, "UpdatePerm")
- })
- }
-}
-
-func TestUpdatePermType(t *testing.T) {
- testCases := []struct {
- perms []acl.Perm
- tcOp
- }{
- {[]acl.Perm{acl.Execute}, tcOp{User, "/tmp/fortify.1971/tmpdir"}},
- {[]acl.Perm{acl.Read, acl.Write, acl.Execute}, tcOp{User, "/tmp/fortify.1971/tmpdir/150"}},
- {[]acl.Perm{acl.Execute}, tcOp{Process, "/run/user/1971/fortify/fcb8a12f7c482d183ade8288c3de78b5"}},
- {[]acl.Perm{acl.Read}, tcOp{Process, "/tmp/fortify.1971/fcb8a12f7c482d183ade8288c3de78b5/passwd"}},
- {[]acl.Perm{acl.Read}, tcOp{Process, "/tmp/fortify.1971/fcb8a12f7c482d183ade8288c3de78b5/group"}},
- {[]acl.Perm{acl.Read, acl.Write, acl.Execute}, tcOp{EWayland, "/run/user/1971/wayland-0"}},
- }
-
- for _, tc := range testCases {
- t.Run(tc.path+"_"+TypeString(tc.et)+permSubTestSuffix(tc.perms), func(t *testing.T) {
- sys := New(150)
- sys.UpdatePermType(tc.et, tc.path, tc.perms...)
- tc.test(t, sys.ops, []Op{&ACL{tc.et, tc.path, tc.perms}}, "UpdatePermType")
- })
- }
-}
-
-func TestACL_String(t *testing.T) {
- testCases := []struct {
- want string
- et Enablement
- perms []acl.Perm
- }{
- {`--- type: Process path: "/nonexistent"`, Process, []acl.Perm{}},
- {`r-- type: User path: "/nonexistent"`, User, []acl.Perm{acl.Read}},
- {`-w- type: Wayland path: "/nonexistent"`, EWayland, []acl.Perm{acl.Write}},
- {`--x type: X11 path: "/nonexistent"`, EX11, []acl.Perm{acl.Execute}},
- {`rw- type: D-Bus path: "/nonexistent"`, EDBus, []acl.Perm{acl.Read, acl.Write}},
- {`r-x type: PulseAudio path: "/nonexistent"`, EPulse, []acl.Perm{acl.Read, acl.Execute}},
- {`rwx type: User path: "/nonexistent"`, User, []acl.Perm{acl.Read, acl.Write, acl.Execute}},
- {`rwx type: Process path: "/nonexistent"`, Process, []acl.Perm{acl.Read, acl.Write, acl.Write, acl.Execute}},
- }
-
- for _, tc := range testCases {
- t.Run(tc.want, func(t *testing.T) {
- a := &ACL{et: tc.et, perms: tc.perms, path: "/nonexistent"}
- if got := a.String(); got != tc.want {
- t.Errorf("String() = %v, want %v",
- got, tc.want)
- }
- })
- }
-}
-
-func permSubTestSuffix(perms []acl.Perm) (suffix string) {
- for _, perm := range perms {
- switch perm {
- case acl.Read:
- suffix += "_read"
- case acl.Write:
- suffix += "_write"
- case acl.Execute:
- suffix += "_execute"
- default:
- panic("unreachable")
- }
- }
- return
-}
diff --git a/internal/system/dbus.go b/internal/system/dbus.go
deleted file mode 100644
index cbb127ed..00000000
--- a/internal/system/dbus.go
+++ /dev/null
@@ -1,148 +0,0 @@
-package system
-
-import (
- "bytes"
- "errors"
- "log"
- "strings"
- "sync"
-
- "git.gensokyo.uk/security/fortify/dbus"
-)
-
-var (
- ErrDBusConfig = errors.New("dbus config not supplied")
-)
-
-func (sys *I) MustProxyDBus(sessionPath string, session *dbus.Config, systemPath string, system *dbus.Config) *I {
- if _, err := sys.ProxyDBus(session, system, sessionPath, systemPath); err != nil {
- panic(err.Error())
- } else {
- return sys
- }
-}
-
-func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath string) (func(), error) {
- d := new(DBus)
-
- // session bus is mandatory
- if session == nil {
- return nil, sys.wrapErr(ErrDBusConfig,
- "attempted to seal message bus proxy without session bus config")
- }
-
- // system bus is optional
- d.system = system != nil
-
- // upstream address, downstream socket path
- var sessionBus, systemBus [2]string
-
- // resolve upstream bus addresses
- sessionBus[0], systemBus[0] = dbus.Address()
-
- // set paths from caller
- sessionBus[1], systemBus[1] = sessionPath, systemPath
-
- // create proxy instance
- d.proxy = dbus.New(sessionBus, systemBus)
-
- defer func() {
- if sys.IsVerbose() && d.proxy.Sealed() {
- sys.println("sealed session proxy", session.Args(sessionBus))
- if system != nil {
- sys.println("sealed system proxy", system.Args(systemBus))
- }
- sys.println("message bus proxy final args:", d.proxy)
- }
- }()
-
- // queue operation
- sys.ops = append(sys.ops, d)
-
- // seal dbus proxy
- d.out = &scanToFmsg{msg: new(strings.Builder)}
- return d.out.Dump, sys.wrapErrSuffix(d.proxy.Seal(session, system),
- "cannot seal message bus proxy:")
-}
-
-type DBus struct {
- proxy *dbus.Proxy
-
- out *scanToFmsg
- // whether system bus proxy is enabled
- system bool
-}
-
-func (d *DBus) Type() Enablement { return Process }
-
-func (d *DBus) apply(sys *I) error {
- sys.printf("session bus proxy on %q for upstream %q", d.proxy.Session()[1], d.proxy.Session()[0])
- if d.system {
- sys.printf("system bus proxy on %q for upstream %q", d.proxy.System()[1], d.proxy.System()[0])
- }
-
- // this starts the process and blocks until ready
- if err := d.proxy.Start(sys.ctx, d.out, true); err != nil {
- d.out.Dump()
- return sys.wrapErrSuffix(err,
- "cannot start message bus proxy:")
- }
- sys.println("starting message bus proxy:", d.proxy)
- return nil
-}
-
-func (d *DBus) revert(sys *I, _ *Criteria) error {
- // criteria ignored here since dbus is always process-scoped
- sys.println("terminating message bus proxy")
- d.proxy.Close()
- defer sys.println("message bus proxy exit")
- return sys.wrapErrSuffix(d.proxy.Wait(), "message bus proxy error:")
-}
-
-func (d *DBus) Is(o Op) bool {
- d0, ok := o.(*DBus)
- return ok && d0 != nil &&
- ((d.proxy == nil && d0.proxy == nil) ||
- (d.proxy != nil && d0.proxy != nil && d.proxy.String() == d0.proxy.String()))
-}
-
-func (d *DBus) Path() string {
- return "(dbus proxy)"
-}
-
-func (d *DBus) String() string {
- return d.proxy.String()
-}
-
-type scanToFmsg struct {
- msg *strings.Builder
- msgbuf []string
-
- mu sync.RWMutex
-}
-
-func (s *scanToFmsg) Write(p []byte) (n int, err error) {
- s.mu.Lock()
- defer s.mu.Unlock()
- return s.write(p, 0)
-}
-
-func (s *scanToFmsg) write(p []byte, a int) (int, error) {
- if i := bytes.IndexByte(p, '\n'); i == -1 {
- n, _ := s.msg.Write(p)
- return a + n, nil
- } else {
- n, _ := s.msg.Write(p[:i])
- s.msgbuf = append(s.msgbuf, s.msg.String())
- s.msg.Reset()
- return s.write(p[i+1:], a+n+1)
- }
-}
-
-func (s *scanToFmsg) Dump() {
- s.mu.RLock()
- for _, msg := range s.msgbuf {
- log.Println(msg)
- }
- s.mu.RUnlock()
-}
diff --git a/internal/system/enablement.go b/internal/system/enablement.go
deleted file mode 100644
index f0911d54..00000000
--- a/internal/system/enablement.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package system
-
-import (
- "strings"
-)
-
-type (
- // Enablement represents an optional system resource
- Enablement uint8
- // Enablements represents optional system resources to share
- Enablements uint64
-)
-
-const (
- EWayland Enablement = iota
- EX11
- EDBus
- EPulse
-)
-
-var enablementString = [...]string{
- EWayland: "Wayland",
- EX11: "X11",
- EDBus: "D-Bus",
- EPulse: "PulseAudio",
-}
-
-const ELen = len(enablementString)
-
-func (e Enablement) String() string {
- if int(e) >= ELen {
- return "<invalid enablement>"
- }
- return enablementString[e]
-}
-
-func (e Enablement) Mask() Enablements {
- return 1 << e
-}
-
-// Has returns whether a feature is enabled
-func (es *Enablements) Has(e Enablement) bool {
- return *es&e.Mask() != 0
-}
-
-// Set enables a feature
-func (es *Enablements) Set(e Enablement) {
- if es.Has(e) {
- panic("enablement " + e.String() + " set twice")
- }
-
- *es |= e.Mask()
-}
-
-func (es *Enablements) String() string {
- buf := new(strings.Builder)
- for i := Enablement(0); i < Enablement(ELen); i++ {
- if es.Has(i) {
- buf.WriteString(", " + i.String())
- }
- }
-
- if buf.Len() == 0 {
- buf.WriteString("(No enablements)")
- }
-
- return strings.TrimPrefix(buf.String(), ", ")
-}
diff --git a/internal/system/link.go b/internal/system/link.go
deleted file mode 100644
index 4182d560..00000000
--- a/internal/system/link.go
+++ /dev/null
@@ -1,47 +0,0 @@
-package system
-
-import (
- "fmt"
- "os"
-)
-
-// 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(sys *I) error {
- sys.println("linking", l)
- return sys.wrapErrSuffix(os.Link(l.src, l.dst),
- fmt.Sprintf("cannot link %q:", l.dst))
-}
-
-func (l *Hardlink) revert(sys *I, ec *Criteria) error {
- if ec.hasType(l) {
- sys.printf("removing hard link %q", l.dst)
- return sys.wrapErrSuffix(os.Remove(l.dst),
- fmt.Sprintf("cannot remove hard link %q:", l.dst))
- } else {
- sys.printf("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) }
diff --git a/internal/system/mkdir.go b/internal/system/mkdir.go
deleted file mode 100644
index 524289e5..00000000
--- a/internal/system/mkdir.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package system
-
-import (
- "errors"
- "fmt"
- "os"
-)
-
-// Ensure the existence and mode of a directory.
-func (sys *I) Ensure(name string, perm os.FileMode) *I {
- sys.lock.Lock()
- defer sys.lock.Unlock()
-
- sys.ops = append(sys.ops, &Mkdir{User, name, perm, false})
-
- return sys
-}
-
-// Ephemeral ensures the temporary existence and mode of a directory through the life of et.
-func (sys *I) Ephemeral(et Enablement, name string, perm os.FileMode) *I {
- sys.lock.Lock()
- defer sys.lock.Unlock()
-
- sys.ops = append(sys.ops, &Mkdir{et, name, perm, true})
-
- return sys
-}
-
-type Mkdir struct {
- et Enablement
- path string
- perm os.FileMode
- ephemeral bool
-}
-
-func (m *Mkdir) Type() Enablement {
- return m.et
-}
-
-func (m *Mkdir) apply(sys *I) error {
- sys.println("ensuring directory", m)
-
- // create directory
- err := os.Mkdir(m.path, m.perm)
- if !errors.Is(err, os.ErrExist) {
- return sys.wrapErrSuffix(err,
- fmt.Sprintf("cannot create directory %q:", m.path))
- }
-
- // directory exists, ensure mode
- return sys.wrapErrSuffix(os.Chmod(m.path, m.perm),
- fmt.Sprintf("cannot change mode of %q to %s:", m.path, m.perm))
-}
-
-func (m *Mkdir) revert(sys *I, ec *Criteria) error {
- if !m.ephemeral {
- // skip non-ephemeral dir and do not log anything
- return nil
- }
-
- if ec.hasType(m) {
- sys.println("destroying ephemeral directory", m)
- return sys.wrapErrSuffix(os.Remove(m.path),
- fmt.Sprintf("cannot remove ephemeral directory %q:", m.path))
- } else {
- sys.println("skipping ephemeral directory", m)
- return nil
- }
-}
-
-func (m *Mkdir) Is(o Op) bool {
- m0, ok := o.(*Mkdir)
- return ok && m0 != nil && *m == *m0
-}
-
-func (m *Mkdir) Path() string {
- return m.path
-}
-
-func (m *Mkdir) String() string {
- t := "Ensure"
- if m.ephemeral {
- t = TypeString(m.Type())
- }
- return fmt.Sprintf("mode: %s type: %s path: %q", m.perm.String(), t, m.path)
-}
diff --git a/internal/system/mkdir_test.go b/internal/system/mkdir_test.go
deleted file mode 100644
index 20826940..00000000
--- a/internal/system/mkdir_test.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package system
-
-import (
- "os"
- "testing"
-)
-
-func TestEnsure(t *testing.T) {
- testCases := []struct {
- name string
- perm os.FileMode
- }{
- {"/tmp/fortify.1971", 0701},
- {"/tmp/fortify.1971/tmpdir", 0700},
- {"/tmp/fortify.1971/tmpdir/150", 0700},
- {"/run/user/1971/fortify", 0700},
- }
- for _, tc := range testCases {
- t.Run(tc.name+"_"+tc.perm.String(), func(t *testing.T) {
- sys := New(150)
- sys.Ensure(tc.name, tc.perm)
- (&tcOp{User, tc.name}).test(t, sys.ops, []Op{&Mkdir{User, tc.name, tc.perm, false}}, "Ensure")
- })
- }
-}
-
-func TestEphemeral(t *testing.T) {
- testCases := []struct {
- perm os.FileMode
- tcOp
- }{
- {0700, tcOp{Process, "/run/user/1971/fortify/ec07546a772a07cde87389afc84ffd13"}},
- {0701, tcOp{Process, "/tmp/fortify.1971/ec07546a772a07cde87389afc84ffd13"}},
- }
- for _, tc := range testCases {
- t.Run(tc.path+"_"+tc.perm.String()+"_"+TypeString(tc.et), func(t *testing.T) {
- sys := New(150)
- sys.Ephemeral(tc.et, tc.path, tc.perm)
- tc.test(t, sys.ops, []Op{&Mkdir{tc.et, tc.path, tc.perm, true}}, "Ephemeral")
- })
- }
-}
-
-func TestMkdir_String(t *testing.T) {
- testCases := []struct {
- want string
- ephemeral bool
- et Enablement
- }{
- {"Ensure", false, User},
- {"Ensure", false, Process},
- {"Ensure", false, EWayland},
-
- {"Wayland", true, EWayland},
- {"X11", true, EX11},
- {"D-Bus", true, EDBus},
- {"PulseAudio", true, EPulse},
- }
- for _, tc := range testCases {
- t.Run(tc.want, func(t *testing.T) {
- m := &Mkdir{
- et: tc.et,
- path: "/nonexistent",
- perm: 0701,
- ephemeral: tc.ephemeral,
- }
- want := "mode: " + os.FileMode(0701).String() + " type: " + tc.want + " path: \"/nonexistent\""
- if got := m.String(); got != want {
- t.Errorf("String() = %v, want %v", got, want)
- }
- })
- }
-}
diff --git a/internal/system/op.go b/internal/system/op.go
deleted file mode 100644
index 0e421aec..00000000
--- a/internal/system/op.go
+++ /dev/null
@@ -1,162 +0,0 @@
-package system
-
-import (
- "context"
- "errors"
- "log"
- "sync"
-)
-
-const (
- // User type is reverted at final launcher exit.
- User = Enablement(ELen)
- // Process type is unconditionally reverted on exit.
- Process = Enablement(ELen + 1)
-)
-
-type Criteria struct {
- *Enablements
-}
-
-func (ec *Criteria) hasType(o Op) bool {
- // nil criteria: revert everything except User
- if ec.Enablements == nil {
- return o.Type() != User
- }
-
- return ec.Has(o.Type())
-}
-
-// Op is a reversible system operation.
-type Op interface {
- // Type returns Op's enablement type.
- Type() Enablement
-
- // apply the Op
- apply(sys *I) error
- // revert reverses the Op if criteria is met
- revert(sys *I, ec *Criteria) error
-
- Is(o Op) bool
- Path() string
- String() string
-}
-
-func TypeString(e Enablement) string {
- switch e {
- case User:
- return "User"
- case Process:
- return "Process"
- default:
- return e.String()
- }
-}
-
-// New initialises sys with no-op verbose functions.
-func New(uid int) (sys *I) {
- sys = new(I)
- sys.uid = uid
- sys.IsVerbose = func() bool { return false }
- sys.Verbose = func(...any) {}
- sys.Verbosef = func(string, ...any) {}
- sys.WrapErr = func(err error, _ ...any) error { return err }
- return
-}
-
-type I struct {
- uid int
- ops []Op
- ctx context.Context
-
- IsVerbose func() bool
- Verbose func(v ...any)
- Verbosef func(format string, v ...any)
- WrapErr func(err error, a ...any) error
-
- // whether sys has been reverted
- state bool
-
- lock sync.Mutex
-}
-
-func (sys *I) UID() int { return sys.uid }
-func (sys *I) println(v ...any) { sys.Verbose(v...) }
-func (sys *I) printf(format string, v ...any) { sys.Verbosef(format, v...) }
-func (sys *I) wrapErr(err error, a ...any) error { return sys.WrapErr(err, a...) }
-func (sys *I) wrapErrSuffix(err error, a ...any) error {
- if err == nil {
- return nil
- }
- return sys.wrapErr(err, append(a, err)...)
-}
-
-func (sys *I) Equal(v *I) bool {
- if v == nil || sys.uid != v.uid || len(sys.ops) != len(v.ops) {
- return false
- }
-
- for i, o := range sys.ops {
- if !o.Is(v.ops[i]) {
- return false
- }
- }
-
- return true
-}
-
-func (sys *I) Commit(ctx context.Context) error {
- sys.lock.Lock()
- defer sys.lock.Unlock()
-
- if sys.ctx != nil {
- panic("sys instance committed twice")
- }
- sys.ctx = ctx
-
- sp := New(sys.uid)
- sp.ops = make([]Op, 0, len(sys.ops)) // prevent copies during commits
- defer func() {
- // sp is set to nil when all ops are applied
- if sp != nil {
- // rollback partial commit
- sys.printf("commit faulted after %d ops, rolling back partial commit", len(sp.ops))
- if err := sp.Revert(&Criteria{nil}); err != nil {
- log.Println("errors returned reverting partial commit:", err)
- }
- }
- }()
-
- for _, o := range sys.ops {
- if err := o.apply(sys); err != nil {
- return err
- } else {
- // register partial commit
- sp.ops = append(sp.ops, o)
- }
- }
-
- // disarm partial commit rollback
- sp = nil
- return nil
-}
-
-func (sys *I) Revert(ec *Criteria) error {
- sys.lock.Lock()
- defer sys.lock.Unlock()
-
- if sys.state {
- panic("sys instance reverted twice")
- }
- sys.state = true
-
- // collect errors
- errs := make([]error, len(sys.ops))
-
- for i := range sys.ops {
- errs[i] = sys.ops[len(sys.ops)-i-1].revert(sys, ec)
- }
-
- // errors.Join filters nils
- return errors.Join(errs...)
-}
diff --git a/internal/system/op_internal_test.go b/internal/system/op_internal_test.go
deleted file mode 100644
index 7ec4f174..00000000
--- a/internal/system/op_internal_test.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package system
-
-import "testing"
-
-type tcOp struct {
- et Enablement
- path string
-}
-
-// test an instance of the Op interface
-func (ptc tcOp) test(t *testing.T, gotOps []Op, wantOps []Op, fn string) {
- if len(gotOps) != len(wantOps) {
- t.Errorf("%s: inserted %v Ops, want %v", fn,
- len(gotOps), len(wantOps))
- return
- }
-
- t.Run("path", func(t *testing.T) {
- if len(gotOps) > 0 {
- if got := gotOps[0].Path(); got != ptc.path {
- t.Errorf("Path() = %q, want %q",
- got, ptc.path)
- return
- }
- }
- })
-
- for i := range gotOps {
- o := gotOps[i]
-
- t.Run("is", func(t *testing.T) {
- if !o.Is(o) {
- t.Errorf("Is returned false on self")
- return
- }
- if !o.Is(wantOps[i]) {
- t.Errorf("%s: inserted %#v, want %#v",
- fn,
- o, wantOps[i])
- return
- }
- })
-
- t.Run("criteria", func(t *testing.T) {
- testCases := []struct {
- name string
- ec *Criteria
- want bool
- }{
- {"nil", newCriteria(), ptc.et != User},
- {"self", newCriteria(ptc.et), true},
- {"all", newCriteria(EWayland, EX11, EDBus, EPulse, User, Process), true},
- {"enablements", newCriteria(EWayland, EX11, EDBus, EPulse), ptc.et != User && ptc.et != Process},
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- if got := tc.ec.hasType(o); got != tc.want {
- t.Errorf("hasType: got %v, want %v",
- got, tc.want)
- }
- })
- }
- })
- }
-}
-
-func newCriteria(labels ...Enablement) *Criteria {
- ec := new(Criteria)
- if len(labels) == 0 {
- return ec
- }
-
- ec.Enablements = new(Enablements)
- for _, e := range labels {
- ec.Set(e)
- }
- return ec
-}
diff --git a/internal/system/op_test.go b/internal/system/op_test.go
deleted file mode 100644
index 3e8c4d2d..00000000
--- a/internal/system/op_test.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package system_test
-
-import (
- "strconv"
- "testing"
-
- "git.gensokyo.uk/security/fortify/internal/system"
-)
-
-func TestNew(t *testing.T) {
- testCases := []struct {
- uid int
- }{
- {150},
- {149},
- {148},
- {147},
- }
-
- for _, tc := range testCases {
- t.Run("sys initialised with uid "+strconv.Itoa(tc.uid), func(t *testing.T) {
- if got := system.New(tc.uid); got.UID() != tc.uid {
- t.Errorf("New(%d) uid = %d, want %d",
- tc.uid,
- got.UID(), tc.uid)
- }
- })
- }
-}
-
-func TestTypeString(t *testing.T) {
- testCases := []struct {
- e system.Enablement
- want string
- }{
- {system.EWayland, system.EWayland.String()},
- {system.EX11, system.EX11.String()},
- {system.EDBus, system.EDBus.String()},
- {system.EPulse, system.EPulse.String()},
- {system.User, "User"},
- {system.Process, "Process"},
- }
-
- for _, tc := range testCases {
- t.Run("label type string "+tc.want, func(t *testing.T) {
- if got := system.TypeString(tc.e); got != tc.want {
- t.Errorf("TypeString(%d) = %v, want %v",
- tc.e,
- got, tc.want)
- }
- })
- }
-}
-
-func TestI_Equal(t *testing.T) {
- testCases := []struct {
- name string
- sys *system.I
- v *system.I
- want bool
- }{
- {
- "simple UID",
- system.New(150),
- system.New(150),
- true,
- },
- {
- "simple UID differ",
- system.New(150),
- system.New(151),
- false,
- },
- {
- "simple UID nil",
- system.New(150),
- nil,
- false,
- },
- {
- "op length mismatch",
- system.New(150).
- ChangeHosts("chronos"),
- system.New(150).
- ChangeHosts("chronos").
- Ensure("/run", 0755),
- false,
- },
- {
- "op value mismatch",
- system.New(150).
- ChangeHosts("chronos").
- Ensure("/run", 0644),
- system.New(150).
- ChangeHosts("chronos").
- Ensure("/run", 0755),
- false,
- },
- {
- "op type mismatch",
- system.New(150).
- ChangeHosts("chronos").
- CopyFile(new([]byte), "/home/ophestra/xdg/config/pulse/cookie", 0, 256),
- system.New(150).
- ChangeHosts("chronos").
- Ensure("/run", 0755),
- false,
- },
- {
- "op equals",
- system.New(150).
- ChangeHosts("chronos").
- Ensure("/run", 0755),
- system.New(150).
- ChangeHosts("chronos").
- Ensure("/run", 0755),
- true,
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- if tc.sys.Equal(tc.v) != tc.want {
- t.Errorf("Equal: got %v; want %v",
- !tc.want, tc.want)
- }
- })
- }
-}
diff --git a/internal/system/tmpfiles.go b/internal/system/tmpfiles.go
deleted file mode 100644
index 636943bc..00000000
--- a/internal/system/tmpfiles.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package system
-
-import (
- "bytes"
- "errors"
- "fmt"
- "io"
- "os"
- "syscall"
-)
-
-// CopyFile registers an Op that copies from src.
-// A buffer is initialised with size cap and the Op faults if bytes read exceed n.
-func (sys *I) CopyFile(payload *[]byte, src string, cap int, n int64) *I {
- buf := new(bytes.Buffer)
- buf.Grow(cap)
-
- sys.lock.Lock()
- sys.ops = append(sys.ops, &Tmpfile{payload, src, n, buf})
- sys.lock.Unlock()
-
- return sys
-}
-
-type Tmpfile struct {
- payload *[]byte
- src string
-
- n int64
- buf *bytes.Buffer
-}
-
-func (t *Tmpfile) Type() Enablement { return Process }
-func (t *Tmpfile) apply(sys *I) error {
- sys.println("copying", t)
-
- if t.payload == nil {
- // this is a misuse of the API; do not return an error message
- return errors.New("invalid payload")
- }
-
- if b, err := os.Stat(t.src); err != nil {
- return sys.wrapErrSuffix(err,
- fmt.Sprintf("cannot stat %q:", t.src))
- } else {
- if b.IsDir() {
- return sys.wrapErrSuffix(syscall.EISDIR,
- fmt.Sprintf("%q is a directory", t.src))
- }
- if s := b.Size(); s > t.n {
- return sys.wrapErrSuffix(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 sys.wrapErrSuffix(err,
- fmt.Sprintf("cannot open %q:", t.src))
- } else if _, err = io.CopyN(t.buf, f, t.n); err != nil {
- return sys.wrapErrSuffix(err,
- fmt.Sprintf("cannot read from %q:", t.src))
- }
-
- *t.payload = t.buf.Bytes()
- return nil
-}
-func (t *Tmpfile) revert(*I, *Criteria) error { t.buf.Reset(); return nil }
-
-func (t *Tmpfile) Is(o Op) bool {
- t0, ok := o.(*Tmpfile)
- return ok && t0 != nil &&
- t.src == t0.src && t.n == t0.n
-}
-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) }
diff --git a/internal/system/tmpfiles_test.go b/internal/system/tmpfiles_test.go
deleted file mode 100644
index c7402f59..00000000
--- a/internal/system/tmpfiles_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package system
-
-import (
- "strconv"
- "testing"
-)
-
-func TestCopyFile(t *testing.T) {
- testCases := []struct {
- tcOp
- cap int
- n int64
- }{
- {tcOp{Process, "/home/ophestra/xdg/config/pulse/cookie"}, 256, 256},
- }
- for _, tc := range testCases {
- t.Run("copy file "+tc.path+" with cap = "+strconv.Itoa(tc.cap)+" n = "+strconv.Itoa(int(tc.n)), func(t *testing.T) {
- sys := New(150)
- sys.CopyFile(new([]byte), tc.path, tc.cap, tc.n)
- tc.test(t, sys.ops, []Op{
- &Tmpfile{nil, tc.path, tc.n, nil},
- }, "CopyFile")
- })
- }
-}
-
-func TestLink(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("link file "+tc.dst+" from "+tc.src, func(t *testing.T) {
- sys := New(150)
- sys.Link(tc.src, tc.dst)
- (&tcOp{Process, tc.src}).test(t, sys.ops, []Op{
- &Hardlink{Process, tc.dst, tc.src},
- }, "Link")
- })
- }
-}
-
-func TestLinkFileType(t *testing.T) {
- testCases := []struct {
- tcOp
- dst string
- }{
- {tcOp{User, "/tmp/fortify.1971/f587afe9fce3c8e1ad5b64deb6c41ad5/pulse-cookie"}, "/home/ophestra/xdg/config/pulse/cookie"},
- {tcOp{Process, "/tmp/fortify.1971/62154f708b5184ab01f9dcc2bbe7a33b/pulse-cookie"}, "/home/ophestra/xdg/config/pulse/cookie"},
- }
- for _, tc := range testCases {
- t.Run("link file "+tc.dst+" from "+tc.path+" with type "+TypeString(tc.et), func(t *testing.T) {
- sys := New(150)
- sys.LinkFileType(tc.et, tc.path, tc.dst)
- tc.test(t, sys.ops, []Op{
- &Hardlink{tc.et, tc.dst, tc.path},
- }, "LinkFileType")
- })
- }
-}
-
-func TestTmpfile_String(t *testing.T) {
- testCases := []struct {
- src string
- n int64
- want string
- }{
- {"/home/ophestra/xdg/config/pulse/cookie", 256,
- `up to 256 bytes from "/home/ophestra/xdg/config/pulse/cookie"`},
- }
-
- for _, tc := range testCases {
- t.Run(tc.want, func(t *testing.T) {
- if got := (&Tmpfile{src: tc.src, n: tc.n}).String(); got != tc.want {
- t.Errorf("String() = %v, want %v", got, tc.want)
- }
- })
- }
-}
diff --git a/internal/system/wayland.go b/internal/system/wayland.go
deleted file mode 100644
index 11f54d80..00000000
--- a/internal/system/wayland.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package system
-
-import (
- "errors"
- "fmt"
- "os"
-
- "git.gensokyo.uk/security/fortify/acl"
- "git.gensokyo.uk/security/fortify/wl"
-)
-
-// Wayland sets up a wayland socket with a security context attached.
-func (sys *I) Wayland(syncFd **os.File, dst, src, appID, instanceID string) *I {
- sys.lock.Lock()
- defer sys.lock.Unlock()
-
- sys.ops = append(sys.ops, &Wayland{syncFd, dst, src, appID, instanceID, wl.Conn{}})
-
- return sys
-}
-
-type Wayland struct {
- sync **os.File
- dst, src string
- appID, instanceID string
-
- conn wl.Conn
-}
-
-func (w *Wayland) Type() Enablement { return Process }
-
-func (w *Wayland) apply(sys *I) error {
- if w.sync == nil {
- // this is a misuse of the API; do not return an error message
- return errors.New("invalid sync")
- }
-
- // the Wayland op is not repeatable
- if *w.sync != nil {
- // this is a misuse of the API; do not return an error message
- return errors.New("attempted to attach multiple wayland sockets")
- }
-
- if err := w.conn.Attach(w.src); err != nil {
- // make console output less nasty
- if errors.Is(err, os.ErrNotExist) {
- err = os.ErrNotExist
- }
- return sys.wrapErrSuffix(err,
- fmt.Sprintf("cannot attach to wayland on %q:", w.src))
- } else {
- sys.printf("wayland attached on %q", w.src)
- }
-
- if sp, err := w.conn.Bind(w.dst, w.appID, w.instanceID); err != nil {
- return sys.wrapErrSuffix(err,
- fmt.Sprintf("cannot bind to socket on %q:", w.dst))
- } else {
- *w.sync = sp
- sys.printf("wayland listening on %q", w.dst)
- return sys.wrapErrSuffix(errors.Join(os.Chmod(w.dst, 0), acl.UpdatePerm(w.dst, sys.uid, acl.Read, acl.Write, acl.Execute)),
- fmt.Sprintf("cannot chmod socket on %q:", w.dst))
- }
-}
-
-func (w *Wayland) revert(sys *I, ec *Criteria) error {
- if ec.hasType(w) {
- sys.printf("removing wayland socket on %q", w.dst)
- if err := os.Remove(w.dst); err != nil && !errors.Is(err, os.ErrNotExist) {
- return err
- }
-
- sys.printf("detaching from wayland on %q", w.src)
- return sys.wrapErrSuffix(w.conn.Close(),
- fmt.Sprintf("cannot detach from wayland on %q:", w.src))
- } else {
- sys.printf("skipping wayland cleanup on %q", w.dst)
- return nil
- }
-}
-
-func (w *Wayland) Is(o Op) bool {
- w0, ok := o.(*Wayland)
- return ok && w.dst == w0.dst && w.src == w0.src &&
- w.appID == w0.appID && w.instanceID == w0.instanceID
-}
-
-func (w *Wayland) Path() string { return w.dst }
-func (w *Wayland) String() string { return fmt.Sprintf("wayland socket at %q", w.dst) }
diff --git a/internal/system/xhost.go b/internal/system/xhost.go
deleted file mode 100644
index bbc0a914..00000000
--- a/internal/system/xhost.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package system
-
-import (
- "fmt"
-
- "git.gensokyo.uk/security/fortify/xcb"
-)
-
-// ChangeHosts appends an X11 ChangeHosts command Op.
-func (sys *I) ChangeHosts(username string) *I {
- sys.lock.Lock()
- defer sys.lock.Unlock()
-
- sys.ops = append(sys.ops, XHost(username))
-
- return sys
-}
-
-type XHost string
-
-func (x XHost) Type() Enablement {
- return EX11
-}
-
-func (x XHost) apply(sys *I) error {
- sys.printf("inserting entry %s to X11", x)
- return sys.wrapErrSuffix(xcb.ChangeHosts(xcb.HostModeInsert, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)),
- fmt.Sprintf("cannot insert entry %s to X11:", x))
-}
-
-func (x XHost) revert(sys *I, ec *Criteria) error {
- if ec.hasType(x) {
- sys.printf("deleting entry %s from X11", x)
- return sys.wrapErrSuffix(xcb.ChangeHosts(xcb.HostModeDelete, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)),
- fmt.Sprintf("cannot delete entry %s from X11:", x))
- } else {
- sys.printf("skipping entry %s in X11", x)
- return nil
- }
-}
-
-func (x XHost) Is(o Op) bool {
- x0, ok := o.(XHost)
- return ok && x == x0
-}
-
-func (x XHost) Path() string {
- return string(x)
-}
-
-func (x XHost) String() string {
- return string("SI:localuser:" + x)
-}
diff --git a/internal/system/xhost_test.go b/internal/system/xhost_test.go
deleted file mode 100644
index 4b364432..00000000
--- a/internal/system/xhost_test.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package system
-
-import (
- "testing"
-)
-
-func TestChangeHosts(t *testing.T) {
- testCases := []string{"chronos", "keyring", "cat", "kbd", "yonah"}
- for _, tc := range testCases {
- t.Run("append ChangeHosts operation for "+tc, func(t *testing.T) {
- sys := New(150)
- sys.ChangeHosts(tc)
- (&tcOp{EX11, tc}).test(t, sys.ops, []Op{
- XHost(tc),
- }, "ChangeHosts")
- })
- }
-}
-
-func TestXHost_String(t *testing.T) {
- testCases := []struct {
- username string
- want string
- }{
- {"chronos", "SI:localuser:chronos"},
- }
- for _, tc := range testCases {
- t.Run(tc.want, func(t *testing.T) {
- if got := XHost(tc.username).String(); got != tc.want {
- t.Errorf("String() = %v, want %v", got, tc.want)
- }
- })
- }
-}