From b72d502f1c5568cd8f93c9142247fa0d94caece5 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Wed, 10 Dec 2025 03:01:30 +0900 Subject: [PATCH] internal/outcome: populate instance metadata for PipeWire These have similar semantics to equivalent Wayland security-context-v1 fields. Signed-off-by: Ophestra --- internal/outcome/run_test.go | 10 +++-- internal/outcome/sppipewire.go | 7 ++- internal/outcome/sppipewire_test.go | 4 +- internal/system/pipewire.go | 12 +++-- internal/system/pipewire_test.go | 70 ++++++++++++++++++++++++++--- 5 files changed, 89 insertions(+), 14 deletions(-) diff --git a/internal/outcome/run_test.go b/internal/outcome/run_test.go index f2aa163..01d487a 100644 --- a/internal/outcome/run_test.go +++ b/internal/outcome/run_test.go @@ -68,7 +68,11 @@ func TestOutcomeRun(t *testing.T) { ). // spPipeWireOp - PipeWire(m("/tmp/hakurei.0/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/pipewire")). + PipeWire( + m("/tmp/hakurei.0/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/pipewire"), + "org.chromium.Chromium", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + ). // spDBusOp MustProxyDBus( @@ -335,7 +339,7 @@ func TestOutcomeRun(t *testing.T) { Ensure(m("/tmp/hakurei.0/tmpdir/9"), 01700).UpdatePermType(system.User, m("/tmp/hakurei.0/tmpdir/9"), acl.Read, acl.Write, acl.Execute). Ephemeral(system.Process, m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c"), 0711). Wayland(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/1971/wayland-0"), "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"). - PipeWire(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/pipewire")). + PipeWire(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/pipewire"), "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"). MustProxyDBus(&hst.BusConfig{ Talk: []string{ "org.freedesktop.Notifications", @@ -486,7 +490,7 @@ func TestOutcomeRun(t *testing.T) { Ensure(m("/run/user/1971/hakurei"), 0700).UpdatePermType(system.User, m("/run/user/1971/hakurei"), acl.Execute). UpdatePermType(hst.EWayland, m("/run/user/1971/wayland-0"), acl.Read, acl.Write, acl.Execute). Ephemeral(system.Process, m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1"), 0711). - PipeWire(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/pipewire")). + PipeWire(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/pipewire"), "org.chromium.Chromium", "8e2c76b066dabe574cf073bdb46eb5c1"). MustProxyDBus(&hst.BusConfig{ Talk: []string{ "org.freedesktop.FileManager1", "org.freedesktop.Notifications", diff --git a/internal/outcome/sppipewire.go b/internal/outcome/sppipewire.go index b8c855f..b9359f7 100644 --- a/internal/outcome/sppipewire.go +++ b/internal/outcome/sppipewire.go @@ -18,7 +18,12 @@ func (s spPipeWireOp) toSystem(state *outcomeStateSys) error { return errNotEnabled } - state.sys.PipeWire(state.instance().Append("pipewire")) + appId := state.appId + if appId == "" { + // use instance ID in case app id is not set + appId = "app.hakurei." + state.id.String() + } + state.sys.PipeWire(state.instance().Append("pipewire"), appId, state.id.String()) return nil } diff --git a/internal/outcome/sppipewire_test.go b/internal/outcome/sppipewire_test.go index d53f6d6..35ad8e5 100644 --- a/internal/outcome/sppipewire_test.go +++ b/internal/outcome/sppipewire_test.go @@ -30,7 +30,9 @@ func TestSpPipeWireOp(t *testing.T) { Ephemeral(system.Process, m(wantInstancePrefix), 0711). // toSystem PipeWire( - m(wantInstancePrefix + "/pipewire"), + m(wantInstancePrefix+"/pipewire"), + "org.chromium.Chromium", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", ), sysUsesInstance(nil), nil, insertsOps(afterSpRuntimeOp(nil)), []stub.Call{ // this op configures the container state and does not make calls during toContainer }, &container.Params{ diff --git a/internal/system/pipewire.go b/internal/system/pipewire.go index 45b1e8b..d04cae4 100644 --- a/internal/system/pipewire.go +++ b/internal/system/pipewire.go @@ -14,8 +14,8 @@ import ( // PipeWire maintains a pipewire socket with SecurityContext attached via [pipewire]. // The socket stops accepting connections once the pipe referred to by sync is closed. // The socket is pathname only and is destroyed on revert. -func (sys *I) PipeWire(dst *check.Absolute) *I { - sys.ops = append(sys.ops, &pipewireOp{nil, dst}) +func (sys *I) PipeWire(dst *check.Absolute, appID, instanceID string) *I { + sys.ops = append(sys.ops, &pipewireOp{nil, dst, appID, instanceID}) return sys } @@ -23,6 +23,8 @@ func (sys *I) PipeWire(dst *check.Absolute) *I { type pipewireOp struct { scc io.Closer dst *check.Absolute + + appID, instanceID string } func (p *pipewireOp) Type() hst.Enablement { return Process } @@ -56,6 +58,8 @@ func (p *pipewireOp) apply(sys *I) (err error) { if p.scc, err = securityContext.BindAndCreate(p.dst.String(), pipewire.SPADict{ {Key: pipewire.PW_KEY_SEC_ENGINE, Value: "app.hakurei"}, + {Key: pipewire.PW_KEY_SEC_APP_ID, Value: p.appID}, + {Key: pipewire.PW_KEY_SEC_INSTANCE_ID, Value: p.instanceID}, {Key: pipewire.PW_KEY_ACCESS, Value: "restricted"}, }); err != nil { return newOpError("pipewire", err, false) @@ -92,7 +96,9 @@ func (p *pipewireOp) revert(sys *I, _ *Criteria) error { func (p *pipewireOp) Is(o Op) bool { target, ok := o.(*pipewireOp) return ok && p != nil && target != nil && - p.dst.Is(target.dst) + p.dst.Is(target.dst) && + p.appID == target.appID && + p.instanceID == target.instanceID } func (p *pipewireOp) Path() string { return p.dst.String() } diff --git a/internal/system/pipewire_test.go b/internal/system/pipewire_test.go index 127e3f2..9c4950c 100644 --- a/internal/system/pipewire_test.go +++ b/internal/system/pipewire_test.go @@ -18,6 +18,8 @@ func TestPipeWireOp(t *testing.T) { checkOpBehaviour(t, checkNoParallel, []opBehaviourTestCase{ {"success", 0xbeef, 0xff, &pipewireOp{nil, m(path.Join(t.TempDir(), "pipewire")), + "org.chromium.Chromium", + "ebf083d1b175911782d413369b64ce7c", }, []stub.Call{ call("pipewireConnect", stub.ExpectArgs{}, func() *pipewire.Context { if ctx, err := pipewire.New(&stubPipeWireConn{sendmsg: []string{ @@ -154,11 +156,11 @@ func TestPipeWireOp(t *testing.T) { string([]byte{ // header: SecurityContext::Create 3, 0, 0, 0, - 0xa8, 0, 0, 1, + 0x40, 1, 0, 1, 5, 0, 0, 0, 2, 0, 0, 0, // Struct - 0xa0, 0, 0, 0, + 0x38, 1, 0, 0, 0xe, 0, 0, 0, // Fd: listen_fd = 1 8, 0, 0, 0, @@ -171,12 +173,12 @@ func TestPipeWireOp(t *testing.T) { 0, 0, 0, 0, 0, 0, 0, 0, // Struct: spa_dict - 0x78, 0, 0, 0, + 0x10, 1, 0, 0, 0xe, 0, 0, 0, - // Int: n_items = 2 + // Int: n_items = 4 + 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 2, 0, 0, 0, 0, 0, 0, 0, // String: key = "pipewire.sec.engine" 0x14, 0, 0, 0, @@ -194,6 +196,48 @@ func TestPipeWireOp(t *testing.T) { 0x68, 0x61, 0x6b, 0x75, 0x72, 0x65, 0x69, 0, 0, 0, 0, 0, + // String: key = "pipewire.sec.app-id" + 0x14, 0, 0, 0, + 8, 0, 0, 0, + 0x70, 0x69, 0x70, 0x65, + 0x77, 0x69, 0x72, 0x65, + 0x2e, 0x73, 0x65, 0x63, + 0x2e, 0x61, 0x70, 0x70, + 0x2d, 0x69, 0x64, 0, + 0, 0, 0, 0, + // String: value = "org.chromium.Chromium" + 0x16, 0, 0, 0, + 8, 0, 0, 0, + 0x6f, 0x72, 0x67, 0x2e, + 0x63, 0x68, 0x72, 0x6f, + 0x6d, 0x69, 0x75, 0x6d, + 0x2e, 0x43, 0x68, 0x72, + 0x6f, 0x6d, 0x69, 0x75, + // String: key = "pipewire.sec.instance-id" + 0x6d, 0, 0, 0, + 0x19, 0, 0, 0, + 8, 0, 0, 0, + 0x70, 0x69, 0x70, 0x65, + 0x77, 0x69, 0x72, 0x65, + 0x2e, 0x73, 0x65, 0x63, + 0x2e, 0x69, 0x6e, 0x73, + 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x2d, 0x69, 0x64, + 0, 0, 0, 0, + 0, 0, 0, 0, + // String: value = "ebf083d1b175911782d413369b64ce7c" + 0x21, 0, 0, 0, + 8, 0, 0, 0, + 0x65, 0x62, 0x66, 0x30, + 0x38, 0x33, 0x64, 0x31, + 0x62, 0x31, 0x37, 0x35, + 0x39, 0x31, 0x31, 0x37, + 0x38, 0x32, 0x64, 0x34, + 0x31, 0x33, 0x33, 0x36, + 0x39, 0x62, 0x36, 0x34, + 0x63, 0x65, 0x37, 0x63, + 0, 0, 0, 0, + 0, 0, 0, 0, // String: key = "pipewire.access" 0x10, 0, 0, 0, 8, 0, 0, 0, @@ -386,29 +430,43 @@ func TestPipeWireOp(t *testing.T) { checkOpsBuilder(t, "PipeWire", []opsBuilderTestCase{ {"sample", 0xcafe, func(_ *testing.T, sys *I) { - sys.PipeWire(m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/pipewire")) + sys.PipeWire(m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/pipewire"), + "org.chromium.Chromium", + "ebf083d1b175911782d413369b64ce7c") }, []Op{&pipewireOp{nil, m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/pipewire"), + "org.chromium.Chromium", + "ebf083d1b175911782d413369b64ce7c", }}, stub.Expect{}}, }) checkOpIs(t, []opIsTestCase{ {"dst differs", &pipewireOp{nil, m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7d/pipewire"), + "org.chromium.Chromium", + "ebf083d1b175911782d413369b64ce7c", }, &pipewireOp{nil, m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/pipewire"), + "org.chromium.Chromium", + "ebf083d1b175911782d413369b64ce7c", }, false}, {"equals", &pipewireOp{nil, m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/pipewire"), + "org.chromium.Chromium", + "ebf083d1b175911782d413369b64ce7c", }, &pipewireOp{nil, m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/pipewire"), + "org.chromium.Chromium", + "ebf083d1b175911782d413369b64ce7c", }, true}, }) checkOpMeta(t, []opMetaTestCase{ {"sample", &pipewireOp{nil, m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/pipewire"), + "org.chromium.Chromium", + "ebf083d1b175911782d413369b64ce7c", }, Process, "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/pipewire", `pipewire socket at "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/pipewire"`}, })