1 Commits

Author SHA1 Message Date
81430987e7 internal/pipewire: integrate pw_security_context
All checks were successful
Test / Create distribution (push) Successful in 6m57s
Test / Sandbox (push) Successful in 8m53s
Test / Hpkg (push) Successful in 10m40s
Test / Sandbox (race detector) (push) Successful in 10m50s
Test / Create distribution (pull_request) Successful in 10m12s
Test / Hakurei (race detector) (push) Successful in 11m27s
Test / Hakurei (race detector) (pull_request) Successful in 11m24s
Test / Sandbox (pull_request) Successful in 40s
Test / Sandbox (race detector) (pull_request) Successful in 40s
Test / Hpkg (pull_request) Successful in 41s
Test / Hakurei (push) Successful in 2m39s
Test / Hakurei (pull_request) Successful in 2m33s
Test / Flake checks (pull_request) Successful in 1m44s
Test / Flake checks (push) Successful in 1m46s
This is required for securely providing access to PipeWire.

This change has already been manually tested and confirmed to work correctly.

This unfortunately cannot be upstreamed in its current state as libpipewire-0.3 breaks static linking.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-19 06:37:30 +09:00
5 changed files with 11 additions and 78 deletions

View File

@@ -12,32 +12,18 @@ import (
type SecurityContext struct {
// Pipe with its write end passed to the PipeWire security context.
closeFds [2]int
// Absolute pathname the socket was bound to.
bindPath *check.Absolute
}
// Close releases any resources held by [SecurityContext], and prevents further
// connections to its associated socket.
//
// A non-nil error has the concrete type [Error].
func (sc *SecurityContext) Close() error {
if sc == nil || sc.bindPath == nil {
if sc == nil {
return os.ErrInvalid
}
e := Error{RCleanup, sc.bindPath.String(), errors.Join(
return errors.Join(
syscall.Close(sc.closeFds[1]),
syscall.Close(sc.closeFds[0]),
// there is still technically a TOCTOU here but this is internal
// and has access to the privileged pipewire socket, so it only
// receives trusted input (e.g. from cmd/hakurei) anyway
os.Remove(sc.bindPath.String()),
)}
if e.Errno != nil {
return &e
}
return nil
)
}
// New creates a new security context on the PipeWire remote at remotePath
@@ -75,11 +61,11 @@ func New(remotePath, bindPath *check.Absolute) (*SecurityContext, error) {
remotePathVal,
closeFds[1],
); err != nil {
return nil, errors.Join(err, // already wrapped
return nil, errors.Join(err,
syscall.Close(closeFds[1]),
syscall.Close(closeFds[0]),
)
} else {
return &SecurityContext{closeFds, bindPath}, nil
return &SecurityContext{closeFds}, nil
}
}

View File

@@ -3,7 +3,6 @@ package pipewire
import (
"errors"
"os"
"path"
"reflect"
"syscall"
"testing"
@@ -12,7 +11,7 @@ import (
)
func TestSecurityContextClose(t *testing.T) {
// do not parallel: fd test not thread safe
t.Parallel()
if err := (*SecurityContext)(nil).Close(); !reflect.DeepEqual(err, os.ErrInvalid) {
t.Fatalf("Close: error = %v", err)
@@ -22,24 +21,13 @@ func TestSecurityContextClose(t *testing.T) {
if err := syscall.Pipe2(ctx.closeFds[0:], syscall.O_CLOEXEC); err != nil {
t.Fatalf("Pipe: error = %v", err)
}
if f, err := os.Create(path.Join(t.TempDir(), "remove")); err != nil {
t.Fatal(err)
} else {
ctx.bindPath = check.MustAbs(f.Name())
}
t.Cleanup(func() { _ = syscall.Close(ctx.closeFds[0]); _ = syscall.Close(ctx.closeFds[1]) })
if err := ctx.Close(); err != nil {
t.Fatalf("Close: error = %v", err)
} else if _, err = os.Stat(ctx.bindPath.String()); err == nil || !errors.Is(err, os.ErrNotExist) {
t.Fatalf("Did not remove %q", ctx.bindPath)
}
wantErr := &Error{Cause: RCleanup, Path: ctx.bindPath.String(), Errno: errors.Join(syscall.EBADF, syscall.EBADF, &os.PathError{
Op: "remove",
Path: ctx.bindPath.String(),
Err: syscall.ENOENT,
})}
wantErr := errors.Join(syscall.EBADF, syscall.EBADF)
if err := ctx.Close(); !reflect.DeepEqual(err, wantErr) {
t.Fatalf("Close: error = %#v, want %#v", err, wantErr)
}

View File

@@ -24,8 +24,6 @@ typedef enum {
/* ensure pathname failed, implemented in conn.go */
HAKUREI_PIPEWIRE_CREAT,
/* cleanup failed, implemented in conn.go */
HAKUREI_PIPEWIRE_CLEANUP,
} hakurei_pipewire_res;
hakurei_pipewire_res hakurei_pw_security_context_bind(

View File

@@ -10,9 +10,7 @@ package pipewire
import "C"
import (
"errors"
"os"
"strings"
"syscall"
)
const (
@@ -71,9 +69,6 @@ const (
// RCreate is returned if ensuring pathname availability failed. Returned by [New].
RCreate Res = C.HAKUREI_PIPEWIRE_CREAT
// RCleanup is returned if cleanup fails. Returned by [SecurityContext.Close].
RCleanup Res = C.HAKUREI_PIPEWIRE_CLEANUP
)
func (e *Error) Unwrap() error { return e.Errno }
@@ -116,19 +111,6 @@ func (e *Error) Error() string {
}
return e.Errno.Error()
case RCleanup:
var pathError *os.PathError
if errors.As(e.Errno, &pathError) && pathError != nil {
return pathError.Error()
}
var errno syscall.Errno
if errors.As(e.Errno, &errno) && errno != 0 {
return "cannot close pipewire close_fd pipe: " + errno.Error()
}
return e.withPrefix("cannot hang up pipewire security context")
default:
return e.withPrefix("impossible outcome") /* not reached */
}

View File

@@ -63,15 +63,15 @@ func TestError(t *testing.T) {
{"bind", Error{
Cause: RBind,
Path: "/tmp/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/pipewire",
Path: "/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/pipewire",
Errno: stub.UniqueError(6),
}, "cannot bind /tmp/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/pipewire: unique error 6 injected by the test suite"},
}, "cannot bind /hakurei.0/18783d07791f2460dbbcffb76c24c9e6/pipewire: unique error 6 injected by the test suite"},
{"listen", Error{
Cause: RListen,
Path: "/tmp/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/pipewire",
Path: "/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/pipewire",
Errno: stub.UniqueError(7),
}, "cannot listen on /tmp/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/pipewire: unique error 7 injected by the test suite"},
}, "cannot listen on /hakurei.0/18783d07791f2460dbbcffb76c24c9e6/pipewire: unique error 7 injected by the test suite"},
{"socket invalid", Error{
Cause: RSocket,
@@ -91,27 +91,6 @@ func TestError(t *testing.T) {
Errno: &os.PathError{Op: "create", Path: "/proc/nonexistent", Err: syscall.EEXIST},
}, "create /proc/nonexistent: file exists"},
{"cleanup", Error{
Cause: RCleanup,
Path: "/tmp/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/pipewire",
}, "cannot hang up pipewire security context"},
{"cleanup PathError", Error{
Cause: RCleanup,
Path: "/tmp/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/pipewire",
Errno: errors.Join(syscall.EINVAL, &os.PathError{
Op: "remove",
Path: "/tmp/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/pipewire",
Err: stub.UniqueError(9),
}),
}, "remove /tmp/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/pipewire: unique error 9 injected by the test suite"},
{"cleanup errno", Error{
Cause: RCleanup,
Path: "/tmp/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/pipewire",
Errno: errors.Join(syscall.EINVAL),
}, "cannot close pipewire close_fd pipe: invalid argument"},
{"invalid", Error{
Cause: 0xbad,
}, "impossible outcome"},