diff --git a/internal/app/share.display.go b/internal/app/share.display.go index 9dbb6c3..51ac54d 100644 --- a/internal/app/share.display.go +++ b/internal/app/share.display.go @@ -42,7 +42,7 @@ func (seal *appSeal) shareDisplay() error { seal.appendEnv(waylandDisplay, wpi) // ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`) - seal.sys.updatePerm(wp, acl.Read, acl.Write, acl.Execute) + seal.sys.updatePermTag(state.EnableWayland, wp, acl.Read, acl.Write, acl.Execute) } } diff --git a/internal/app/share.runtime.go b/internal/app/share.runtime.go index 444d85a..b015d45 100644 --- a/internal/app/share.runtime.go +++ b/internal/app/share.runtime.go @@ -4,6 +4,7 @@ import ( "path" "git.ophivana.moe/cat/fortify/acl" + "git.ophivana.moe/cat/fortify/internal/state" ) const ( @@ -16,10 +17,10 @@ const ( func (seal *appSeal) shareRuntime() { // ensure RunDir (e.g. `/run/user/%d/fortify`) seal.sys.ensure(seal.RunDirPath, 0700) - seal.sys.updatePerm(seal.RunDirPath, acl.Execute) + seal.sys.updatePermTag(state.EnableLength, seal.RunDirPath, acl.Execute) // ensure runtime directory ACL (e.g. `/run/user/%d`) - seal.sys.updatePerm(seal.RuntimePath, acl.Execute) + seal.sys.updatePermTag(state.EnableLength, seal.RuntimePath, acl.Execute) // ensure Share (e.g. `/tmp/fortify.%d`) // acl is unnecessary as this directory is world executable @@ -40,12 +41,12 @@ func (seal *appSeal) shareRuntimeChild() string { // ensure child runtime parent directory (e.g. `/tmp/fortify.%d/runtime`) targetRuntimeParent := path.Join(seal.SharePath, "runtime") seal.sys.ensure(targetRuntimeParent, 0700) - seal.sys.updatePerm(targetRuntimeParent, acl.Execute) + seal.sys.updatePermTag(state.EnableLength, targetRuntimeParent, acl.Execute) // ensure child runtime directory (e.g. `/tmp/fortify.%d/runtime/%d`) targetRuntime := path.Join(targetRuntimeParent, seal.sys.Uid) seal.sys.ensure(targetRuntime, 0700) - seal.sys.updatePerm(targetRuntime, acl.Read, acl.Write, acl.Execute) + seal.sys.updatePermTag(state.EnableLength, targetRuntime, acl.Read, acl.Write, acl.Execute) // point to ensured runtime path seal.appendEnv(xdgRuntimeDir, targetRuntime) diff --git a/internal/app/start.go b/internal/app/start.go index db6c5f1..02fad97 100644 --- a/internal/app/start.go +++ b/internal/app/start.go @@ -155,24 +155,42 @@ func (a *app) Wait() (int, error) { return err } - var global bool - - // measure remaining state entries - if l, err := b.Len(); err != nil { + // enablements of remaining launchers + rt, tags := new(state.Enablements), new(state.Enablements) + tags.Set(state.EnableLength + 1) + if states, err := b.Load(); err != nil { return err } else { - // clean up global modifications if we're the last launcher alive - global = l == 0 - - if !global { - verbose.Printf("found %d active launchers, cleaning up without globals\n", l) - } else { + if l := len(states); l == 0 { + // cleanup globals as the final launcher verbose.Println("no other launchers active, will clean up globals") + tags.Set(state.EnableLength) + } else { + verbose.Printf("found %d active launchers, cleaning up without globals\n", l) + } + + // accumulate capabilities of other launchers + for _, s := range states { + *rt |= s.Capability } } + // invert accumulated enablements for cleanup + for i := state.Enablement(0); i < state.EnableLength; i++ { + if !rt.Has(i) { + tags.Set(i) + } + } + if verbose.Get() { + ct := make([]state.Enablement, 0, state.EnableLength) + for i := state.Enablement(0); i < state.EnableLength; i++ { + if tags.Has(i) { + ct = append(ct, i) + } + } + verbose.Println("will revert operations tagged", ct, "as no remaining launchers hold these enablements") + } - // FIXME: depending on exit sequence, some parts of the transaction never gets reverted - if err := a.seal.sys.revert(global); err != nil { + if err := a.seal.sys.revert(tags); err != nil { return err.(RevertCompoundError) } diff --git a/internal/app/system.go b/internal/app/system.go index f328502..aa62cfb 100644 --- a/internal/app/system.go +++ b/internal/app/system.go @@ -112,10 +112,22 @@ func (tx *appSealTx) ensureEphemeral(path string, perm os.FileMode) { // appACLEntry contains information for applying/reverting an ACL entry type appACLEntry struct { + tag state.Enablement path string perms []acl.Perm } +func (e *appACLEntry) ts() string { + switch e.tag { + case state.EnableLength: + return "Global" + case state.EnableLength + 1: + return "Process" + default: + return e.tag.String() + } +} + func (e *appACLEntry) String() string { var s = []byte("---") for _, p := range e.perms { @@ -131,9 +143,16 @@ func (e *appACLEntry) String() string { return string(s) } -// updatePerm appends an acl update action +// updatePerm appends an untagged acl update action func (tx *appSealTx) updatePerm(path string, perms ...acl.Perm) { - tx.acl = append(tx.acl, &appACLEntry{path, perms}) + tx.updatePermTag(state.EnableLength+1, path, perms...) +} + +// updatePermTag appends an acl update action +// Tagging with state.EnableLength sets cleanup to happen at final active launcher exit, +// while tagging with state.EnableLength+1 will unconditionally clean up on exit. +func (tx *appSealTx) updatePermTag(tag state.Enablement, path string, perms ...acl.Perm) { + tx.acl = append(tx.acl, &appACLEntry{tag, path, perms}) } // changeHosts appends target username of an X11 ChangeHosts action @@ -175,7 +194,11 @@ func (tx *appSealTx) commit() error { // global changes (x11, ACLs) are always repeated and check for other launchers cannot happen here // attempting cleanup here will cause other fortified processes to lose access to them // a better (and more secure) fix is to proxy access to these resources and eliminate the ACLs altogether - if err := txp.revert(false); err != nil { + tags := new(state.Enablements) + for e := state.Enablement(0); e < state.EnableLength+2; e++ { + tags.Set(e) + } + if err := txp.revert(tags); err != nil { fmt.Println("fortify: errors returned reverting partial commit:", err) } } @@ -250,13 +273,13 @@ func (tx *appSealTx) commit() error { // apply ACLs for _, e := range tx.acl { - verbose.Println("applying ACL", e, "uid:", tx.Uid, "path:", e.path) + verbose.Println("applying ACL", e, "uid:", tx.Uid, "tag:", e.ts(), "path:", e.path) if err := acl.UpdatePerm(e.path, tx.uid, e.perms...); err != nil { return (*ACLUpdateError)(wrapError(err, fmt.Sprintf("cannot apply ACL to '%s': %s", e.path, err))) } else { // register partial commit - txp.updatePerm(e.path, e.perms...) + txp.updatePermTag(e.tag, e.path, e.perms...) } } @@ -268,7 +291,7 @@ func (tx *appSealTx) commit() error { // revert rolls back recorded actions // order: acl, dbus, hardlinks, tmpfiles, mkdir, xhost // errors are printed but not treated as fatal -func (tx *appSealTx) revert(global bool) error { +func (tx *appSealTx) revert(tags *state.Enablements) error { if tx.closed { panic("seal transaction reverted twice") } @@ -284,12 +307,14 @@ func (tx *appSealTx) revert(global bool) error { errs = append(errs, e) } - if global { - // revert ACLs - for _, e := range tx.acl { - verbose.Println("stripping ACL", e, "uid:", tx.Uid, "path:", e.path) + // revert ACLs + for _, e := range tx.acl { + if tags.Has(e.tag) { + verbose.Println("stripping ACL", e, "uid:", tx.Uid, "tag:", e.ts(), "path:", e.path) err := acl.UpdatePerm(e.path, tx.uid) joinError(err, fmt.Sprintf("cannot strip ACL entry from '%s': %s", e.path, err)) + } else { + verbose.Println("skipping ACL", e, "uid:", tx.Uid, "tag:", e.ts(), "path:", e.path) } } @@ -326,7 +351,7 @@ func (tx *appSealTx) revert(global bool) error { joinError(err, fmt.Sprintf("cannot remove ephemeral directory '%s': %s", dir.path, err)) } - if global { + if tags.Has(state.EnableX) { // rollback xhost insertions for _, username := range tx.xhost { verbose.Printf("deleting XHost entry SI:localuser:%s\n", username)