@@ -5,7 +5,7 @@ import (
|
||||
"git.gensokyo.uk/security/fortify/dbus"
|
||||
"git.gensokyo.uk/security/fortify/fst"
|
||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||
"git.gensokyo.uk/security/fortify/internal/system"
|
||||
"git.gensokyo.uk/security/fortify/system"
|
||||
)
|
||||
|
||||
var testCasesNixos = []sealTestCase{
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"git.gensokyo.uk/security/fortify/dbus"
|
||||
"git.gensokyo.uk/security/fortify/fst"
|
||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||
"git.gensokyo.uk/security/fortify/internal/system"
|
||||
"git.gensokyo.uk/security/fortify/system"
|
||||
)
|
||||
|
||||
var testCasesPd = []sealTestCase{
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||
"git.gensokyo.uk/security/fortify/internal/app"
|
||||
"git.gensokyo.uk/security/fortify/internal/linux"
|
||||
"git.gensokyo.uk/security/fortify/internal/system"
|
||||
"git.gensokyo.uk/security/fortify/system"
|
||||
)
|
||||
|
||||
type sealTestCase struct {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"git.gensokyo.uk/security/fortify/fst"
|
||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||
"git.gensokyo.uk/security/fortify/internal/linux"
|
||||
"git.gensokyo.uk/security/fortify/internal/system"
|
||||
"git.gensokyo.uk/security/fortify/system"
|
||||
)
|
||||
|
||||
func NewWithID(id fst.ID, os linux.System) App {
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||
"git.gensokyo.uk/security/fortify/internal/linux"
|
||||
"git.gensokyo.uk/security/fortify/internal/state"
|
||||
"git.gensokyo.uk/security/fortify/internal/system"
|
||||
"git.gensokyo.uk/security/fortify/system"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"git.gensokyo.uk/security/fortify/fst"
|
||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||
"git.gensokyo.uk/security/fortify/internal/linux"
|
||||
"git.gensokyo.uk/security/fortify/internal/system"
|
||||
"git.gensokyo.uk/security/fortify/system"
|
||||
"git.gensokyo.uk/security/fortify/wl"
|
||||
)
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"git.gensokyo.uk/security/fortify/internal/app/shim"
|
||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||
"git.gensokyo.uk/security/fortify/internal/state"
|
||||
"git.gensokyo.uk/security/fortify/internal/system"
|
||||
"git.gensokyo.uk/security/fortify/system"
|
||||
)
|
||||
|
||||
const shimSetupTimeout = 5 * time.Second
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
|
||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||
"git.gensokyo.uk/security/fortify/internal/system"
|
||||
"git.gensokyo.uk/security/fortify/system"
|
||||
)
|
||||
|
||||
// appSealSys encapsulates app seal behaviour with OS interactions
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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(), ", ")
|
||||
}
|
||||
@@ -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) }
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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...)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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) }
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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) }
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user