diff --git a/internal/app/share.display.go b/internal/app/share.display.go index c189187..9dbb6c3 100644 --- a/internal/app/share.display.go +++ b/internal/app/share.display.go @@ -35,9 +35,11 @@ func (seal *appSeal) shareDisplay() error { if wd, ok := os.LookupEnv(waylandDisplay); !ok { return (*ErrDisplayEnv)(wrapError(ErrWayland, "WAYLAND_DISPLAY is not set")) } else { - // wayland socket path + // hardlink wayland socket 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`) seal.sys.updatePerm(wp, acl.Read, acl.Write, acl.Execute) diff --git a/internal/app/share.pulse.go b/internal/app/share.pulse.go index 633b859..b432345 100644 --- a/internal/app/share.pulse.go +++ b/internal/app/share.pulse.go @@ -7,7 +7,6 @@ import ( "os" "path" - "git.ophivana.moe/cat/fortify/acl" "git.ophivana.moe/cat/fortify/internal/state" ) @@ -35,7 +34,7 @@ func (seal *appSeal) sharePulse() error { 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") ps := path.Join(pd, "native") if _, err := os.Stat(pd); err != nil { @@ -47,10 +46,7 @@ func (seal *appSeal) sharePulse() error { fmt.Sprintf("PulseAudio directory '%s' not found", pd))) } - seal.appendEnv(pulseServer, "unix:"+ps) - seal.sys.updatePerm(pd, acl.Execute) - - // ensure PulseAudio socket permission (e.g. `/run/user/%d/pulse/native`) + // check PulseAudio socket permission (e.g. `/run/user/%d/pulse/native`) if s, err := os.Stat(ps); err != nil { if !errors.Is(err, fs.ErrNotExist) { 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 if src, err := discoverPulseCookie(); err != nil { return err diff --git a/internal/app/share.runtime.go b/internal/app/share.runtime.go index e43effe..444d85a 100644 --- a/internal/app/share.runtime.go +++ b/internal/app/share.runtime.go @@ -16,6 +16,7 @@ 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) // ensure runtime directory ACL (e.g. `/run/user/%d`) seal.sys.updatePerm(seal.RuntimePath, acl.Execute) @@ -28,6 +29,11 @@ func (seal *appSeal) shareRuntime() { // acl is unnecessary as this directory is world executable seal.share = path.Join(seal.SharePath, seal.id.String()) 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 { diff --git a/internal/app/system.go b/internal/app/system.go index 86d97d9..f328502 100644 --- a/internal/app/system.go +++ b/internal/app/system.go @@ -33,6 +33,8 @@ type appSeal struct { launchOption uint8 // process-specific share directory path share string + // process-specific share directory path local to XDG_RUNTIME_DIR + shareLocal string // path to launcher program toolPath string @@ -74,6 +76,8 @@ type appSealTx struct { mkdir []appEnsureEntry // dst, src pairs of temporarily shared files tmpfiles [][2]string + // dst, src pairs of temporarily hard linked files + hardlinks [][2]string // sealed path to fortify executable, used by shim executable string @@ -143,6 +147,11 @@ func (tx *appSealTx) copyFile(dst, src string) { 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 ( ChangeHostsError BaseError EnsureDirError BaseError @@ -152,7 +161,7 @@ type ( ) // commit applies recorded actions -// order: xhost, mkdir, tmpfiles, dbus, acl +// order: xhost, mkdir, tmpfiles, hardlinks, dbus, acl func (tx *appSealTx) commit() error { if tx.complete { 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 { // start dbus proxy 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 -// order: acl, dbus, tmpfiles, mkdir, xhost +// order: acl, dbus, hardlinks, tmpfiles, mkdir, xhost // errors are printed but not treated as fatal func (tx *appSealTx) revert(global bool) error { if tx.closed { @@ -279,6 +300,13 @@ func (tx *appSealTx) revert(global bool) error { 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 for _, tmpfile := range tx.tmpfiles { verbose.Println("removing tmpfile", tmpfile[0])