app: tag ACL operations for revert
ACL operations are now tagged with the enablement causing them. At the end of child process's life, enablements of all remaining launchers are resolved and inverted. This allows Wait to only revert operations targeting resources no longer required by other launchers. Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
This commit is contained in:
parent
86cb5ac1db
commit
a3aadd4146
|
@ -42,7 +42,7 @@ func (seal *appSeal) shareDisplay() error {
|
||||||
seal.appendEnv(waylandDisplay, wpi)
|
seal.appendEnv(waylandDisplay, wpi)
|
||||||
|
|
||||||
// ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`)
|
// 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"git.ophivana.moe/cat/fortify/acl"
|
"git.ophivana.moe/cat/fortify/acl"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -16,10 +17,10 @@ const (
|
||||||
func (seal *appSeal) shareRuntime() {
|
func (seal *appSeal) shareRuntime() {
|
||||||
// ensure RunDir (e.g. `/run/user/%d/fortify`)
|
// ensure RunDir (e.g. `/run/user/%d/fortify`)
|
||||||
seal.sys.ensure(seal.RunDirPath, 0700)
|
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`)
|
// 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`)
|
// ensure Share (e.g. `/tmp/fortify.%d`)
|
||||||
// acl is unnecessary as this directory is world executable
|
// 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`)
|
// ensure child runtime parent directory (e.g. `/tmp/fortify.%d/runtime`)
|
||||||
targetRuntimeParent := path.Join(seal.SharePath, "runtime")
|
targetRuntimeParent := path.Join(seal.SharePath, "runtime")
|
||||||
seal.sys.ensure(targetRuntimeParent, 0700)
|
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`)
|
// ensure child runtime directory (e.g. `/tmp/fortify.%d/runtime/%d`)
|
||||||
targetRuntime := path.Join(targetRuntimeParent, seal.sys.Uid)
|
targetRuntime := path.Join(targetRuntimeParent, seal.sys.Uid)
|
||||||
seal.sys.ensure(targetRuntime, 0700)
|
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
|
// point to ensured runtime path
|
||||||
seal.appendEnv(xdgRuntimeDir, targetRuntime)
|
seal.appendEnv(xdgRuntimeDir, targetRuntime)
|
||||||
|
|
|
@ -155,24 +155,42 @@ func (a *app) Wait() (int, error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var global bool
|
// enablements of remaining launchers
|
||||||
|
rt, tags := new(state.Enablements), new(state.Enablements)
|
||||||
// measure remaining state entries
|
tags.Set(state.EnableLength + 1)
|
||||||
if l, err := b.Len(); err != nil {
|
if states, err := b.Load(); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
// clean up global modifications if we're the last launcher alive
|
if l := len(states); l == 0 {
|
||||||
global = l == 0
|
// cleanup globals as the final launcher
|
||||||
|
|
||||||
if !global {
|
|
||||||
verbose.Printf("found %d active launchers, cleaning up without globals\n", l)
|
|
||||||
} else {
|
|
||||||
verbose.Println("no other launchers active, will clean up globals")
|
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(tags); err != nil {
|
||||||
if err := a.seal.sys.revert(global); err != nil {
|
|
||||||
return err.(RevertCompoundError)
|
return err.(RevertCompoundError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -112,10 +112,22 @@ func (tx *appSealTx) ensureEphemeral(path string, perm os.FileMode) {
|
||||||
|
|
||||||
// appACLEntry contains information for applying/reverting an ACL entry
|
// appACLEntry contains information for applying/reverting an ACL entry
|
||||||
type appACLEntry struct {
|
type appACLEntry struct {
|
||||||
|
tag state.Enablement
|
||||||
path string
|
path string
|
||||||
perms []acl.Perm
|
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 {
|
func (e *appACLEntry) String() string {
|
||||||
var s = []byte("---")
|
var s = []byte("---")
|
||||||
for _, p := range e.perms {
|
for _, p := range e.perms {
|
||||||
|
@ -131,9 +143,16 @@ func (e *appACLEntry) String() string {
|
||||||
return string(s)
|
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) {
|
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
|
// 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
|
// 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
|
// 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
|
// 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)
|
fmt.Println("fortify: errors returned reverting partial commit:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -250,13 +273,13 @@ func (tx *appSealTx) commit() error {
|
||||||
|
|
||||||
// apply ACLs
|
// apply ACLs
|
||||||
for _, e := range tx.acl {
|
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 {
|
if err := acl.UpdatePerm(e.path, tx.uid, e.perms...); err != nil {
|
||||||
return (*ACLUpdateError)(wrapError(err,
|
return (*ACLUpdateError)(wrapError(err,
|
||||||
fmt.Sprintf("cannot apply ACL to '%s': %s", e.path, err)))
|
fmt.Sprintf("cannot apply ACL to '%s': %s", e.path, err)))
|
||||||
} else {
|
} else {
|
||||||
// register partial commit
|
// 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
|
// revert rolls back recorded actions
|
||||||
// order: acl, dbus, hardlinks, tmpfiles, mkdir, xhost
|
// order: acl, dbus, hardlinks, tmpfiles, mkdir, xhost
|
||||||
// errors are printed but not treated as fatal
|
// 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 {
|
if tx.closed {
|
||||||
panic("seal transaction reverted twice")
|
panic("seal transaction reverted twice")
|
||||||
}
|
}
|
||||||
|
@ -284,12 +307,14 @@ func (tx *appSealTx) revert(global bool) error {
|
||||||
errs = append(errs, e)
|
errs = append(errs, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
if global {
|
// revert ACLs
|
||||||
// revert ACLs
|
for _, e := range tx.acl {
|
||||||
for _, e := range tx.acl {
|
if tags.Has(e.tag) {
|
||||||
verbose.Println("stripping ACL", e, "uid:", tx.Uid, "path:", e.path)
|
verbose.Println("stripping ACL", e, "uid:", tx.Uid, "tag:", e.ts(), "path:", e.path)
|
||||||
err := acl.UpdatePerm(e.path, tx.uid)
|
err := acl.UpdatePerm(e.path, tx.uid)
|
||||||
joinError(err, fmt.Sprintf("cannot strip ACL entry from '%s': %s", e.path, err))
|
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))
|
joinError(err, fmt.Sprintf("cannot remove ephemeral directory '%s': %s", dir.path, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if global {
|
if tags.Has(state.EnableX) {
|
||||||
// rollback xhost insertions
|
// rollback xhost insertions
|
||||||
for _, username := range tx.xhost {
|
for _, username := range tx.xhost {
|
||||||
verbose.Printf("deleting XHost entry SI:localuser:%s\n", username)
|
verbose.Printf("deleting XHost entry SI:localuser:%s\n", username)
|
||||||
|
|
Loading…
Reference in New Issue