app: hardlink sockets to process-specific share local to XDG_RUNTIME_DIR

This avoids adding ACLs to the PulseAudio directory.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
This commit is contained in:
Ophestra Umiker 2024-10-10 12:44:08 +09:00
parent 2220055e26
commit 86cb5ac1db
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
4 changed files with 47 additions and 10 deletions

View File

@ -35,9 +35,11 @@ func (seal *appSeal) shareDisplay() error {
if wd, ok := os.LookupEnv(waylandDisplay); !ok { if wd, ok := os.LookupEnv(waylandDisplay); !ok {
return (*ErrDisplayEnv)(wrapError(ErrWayland, "WAYLAND_DISPLAY is not set")) return (*ErrDisplayEnv)(wrapError(ErrWayland, "WAYLAND_DISPLAY is not set"))
} else { } else {
// wayland socket path // hardlink wayland socket
wp := path.Join(seal.RuntimePath, wd) wp := path.Join(seal.RuntimePath, wd)
seal.appendEnv(waylandDisplay, wp) wpi := path.Join(seal.shareLocal, "wayland")
seal.sys.link(wp, 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.updatePerm(wp, acl.Read, acl.Write, acl.Execute)

View File

@ -7,7 +7,6 @@ import (
"os" "os"
"path" "path"
"git.ophivana.moe/cat/fortify/acl"
"git.ophivana.moe/cat/fortify/internal/state" "git.ophivana.moe/cat/fortify/internal/state"
) )
@ -35,7 +34,7 @@ func (seal *appSeal) sharePulse() error {
return nil return nil
} }
// ensure PulseAudio directory ACL (e.g. `/run/user/%d/pulse`) // check PulseAudio directory presence (e.g. `/run/user/%d/pulse`)
pd := path.Join(seal.RuntimePath, "pulse") pd := path.Join(seal.RuntimePath, "pulse")
ps := path.Join(pd, "native") ps := path.Join(pd, "native")
if _, err := os.Stat(pd); err != nil { if _, err := os.Stat(pd); err != nil {
@ -47,10 +46,7 @@ func (seal *appSeal) sharePulse() error {
fmt.Sprintf("PulseAudio directory '%s' not found", pd))) fmt.Sprintf("PulseAudio directory '%s' not found", pd)))
} }
seal.appendEnv(pulseServer, "unix:"+ps) // check PulseAudio socket permission (e.g. `/run/user/%d/pulse/native`)
seal.sys.updatePerm(pd, acl.Execute)
// ensure PulseAudio socket permission (e.g. `/run/user/%d/pulse/native`)
if s, err := os.Stat(ps); err != nil { if s, err := os.Stat(ps); err != nil {
if !errors.Is(err, fs.ErrNotExist) { if !errors.Is(err, fs.ErrNotExist) {
return (*PulseSocketAccessError)(wrapError(err, return (*PulseSocketAccessError)(wrapError(err,
@ -65,6 +61,11 @@ func (seal *appSeal) sharePulse() error {
} }
} }
// hard link pulse socket into target-executable share
psi := path.Join(seal.shareLocal, "pulse")
seal.sys.link(ps, psi)
seal.appendEnv(pulseServer, "unix:"+psi)
// publish current user's pulse cookie for target user // publish current user's pulse cookie for target user
if src, err := discoverPulseCookie(); err != nil { if src, err := discoverPulseCookie(); err != nil {
return err return err

View File

@ -16,6 +16,7 @@ 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)
// 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.updatePerm(seal.RuntimePath, acl.Execute)
@ -28,6 +29,11 @@ func (seal *appSeal) shareRuntime() {
// acl is unnecessary as this directory is world executable // acl is unnecessary as this directory is world executable
seal.share = path.Join(seal.SharePath, seal.id.String()) seal.share = path.Join(seal.SharePath, seal.id.String())
seal.sys.ensureEphemeral(seal.share, 0701) seal.sys.ensureEphemeral(seal.share, 0701)
// ensure process-specific share local to XDG_RUNTIME_DIR (e.g. `/run/user/%d/fortify/%s`)
seal.shareLocal = path.Join(seal.RunDirPath, seal.id.String())
seal.sys.ensureEphemeral(seal.shareLocal, 0700)
seal.sys.updatePerm(seal.shareLocal, acl.Execute)
} }
func (seal *appSeal) shareRuntimeChild() string { func (seal *appSeal) shareRuntimeChild() string {

View File

@ -33,6 +33,8 @@ type appSeal struct {
launchOption uint8 launchOption uint8
// process-specific share directory path // process-specific share directory path
share string share string
// process-specific share directory path local to XDG_RUNTIME_DIR
shareLocal string
// path to launcher program // path to launcher program
toolPath string toolPath string
@ -74,6 +76,8 @@ type appSealTx struct {
mkdir []appEnsureEntry mkdir []appEnsureEntry
// dst, src pairs of temporarily shared files // dst, src pairs of temporarily shared files
tmpfiles [][2]string tmpfiles [][2]string
// dst, src pairs of temporarily hard linked files
hardlinks [][2]string
// sealed path to fortify executable, used by shim // sealed path to fortify executable, used by shim
executable string executable string
@ -143,6 +147,11 @@ func (tx *appSealTx) copyFile(dst, src string) {
tx.updatePerm(dst, acl.Read) tx.updatePerm(dst, acl.Read)
} }
// link appends a hardlink action
func (tx *appSealTx) link(oldname, newname string) {
tx.hardlinks = append(tx.hardlinks, [2]string{oldname, newname})
}
type ( type (
ChangeHostsError BaseError ChangeHostsError BaseError
EnsureDirError BaseError EnsureDirError BaseError
@ -152,7 +161,7 @@ type (
) )
// commit applies recorded actions // commit applies recorded actions
// order: xhost, mkdir, tmpfiles, dbus, acl // order: xhost, mkdir, tmpfiles, hardlinks, dbus, acl
func (tx *appSealTx) commit() error { func (tx *appSealTx) commit() error {
if tx.complete { if tx.complete {
panic("seal transaction committed twice") panic("seal transaction committed twice")
@ -211,6 +220,18 @@ func (tx *appSealTx) commit() error {
} }
} }
// create hardlinks
for _, link := range tx.hardlinks {
verbose.Println("creating hardlink", link[1], "from", link[0])
if err := os.Link(link[0], link[1]); err != nil {
return (*TmpfileError)(wrapError(err,
fmt.Sprintf("cannot create hardlink '%s' from '%s': %s", link[1], link[0], err)))
} else {
// register partial commit
txp.link(link[0], link[1])
}
}
if tx.dbus != nil { if tx.dbus != nil {
// start dbus proxy // start dbus proxy
verbose.Printf("session bus proxy on '%s' for upstream '%s'\n", tx.dbusAddr[0][1], tx.dbusAddr[0][0]) verbose.Printf("session bus proxy on '%s' for upstream '%s'\n", tx.dbusAddr[0][1], tx.dbusAddr[0][0])
@ -245,7 +266,7 @@ func (tx *appSealTx) commit() error {
} }
// revert rolls back recorded actions // revert rolls back recorded actions
// order: acl, dbus, 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(global bool) error {
if tx.closed { if tx.closed {
@ -279,6 +300,13 @@ func (tx *appSealTx) revert(global bool) error {
joinError(err, "cannot stop message bus proxy:", err) joinError(err, "cannot stop message bus proxy:", err)
} }
// remove hardlinks
for _, link := range tx.hardlinks {
verbose.Println("removing hardlink", link[1])
err := os.Remove(link[1])
joinError(err, fmt.Sprintf("cannot remove hardlink '%s': %s", link[1], err))
}
// remove tmpfiles // remove tmpfiles
for _, tmpfile := range tx.tmpfiles { for _, tmpfile := range tx.tmpfiles {
verbose.Println("removing tmpfile", tmpfile[0]) verbose.Println("removing tmpfile", tmpfile[0])