app: move wayland mediation to shim package
All checks were successful
test / test (push) Successful in 29s

Values used in the Wayland mediation implementation is stored in various struct fields strewn across multiple app structs and checks are messy and confusing. This commit unifies them into a single struct and access it using much better looking methods.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
This commit is contained in:
Ophestra 2024-10-20 22:54:47 +09:00
parent 133f23e0de
commit 380d1f4585
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
7 changed files with 94 additions and 73 deletions

View File

@ -1,7 +1,6 @@
package app
import (
"net"
"os/exec"
"sync"
)
@ -27,9 +26,6 @@ type app struct {
cmd *exec.Cmd
// child process related information
seal *appSeal
// wayland connection if wayland mediation is enabled
wayland *net.UnixConn
// error returned waiting for process
waitErr error

View File

@ -11,6 +11,7 @@ import (
"git.ophivana.moe/security/fortify/dbus"
"git.ophivana.moe/security/fortify/internal"
"git.ophivana.moe/security/fortify/internal/fmsg"
"git.ophivana.moe/security/fortify/internal/shim"
"git.ophivana.moe/security/fortify/internal/state"
"git.ophivana.moe/security/fortify/internal/system"
"git.ophivana.moe/security/fortify/internal/verbose"
@ -36,6 +37,43 @@ var (
ErrMachineCtl = errors.New("machinectl not available")
)
// appSeal seals the application with child-related information
type appSeal struct {
// app unique ID string representation
id string
// wayland mediation, disabled if nil
wl *shim.Wayland
// freedesktop application ID
fid string
// argv to start process with in the final confined environment
command []string
// persistent process state store
store state.Store
// uint8 representation of launch method sealed from config
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
// pass-through enablement tracking from config
et system.Enablements
// prevents sharing from happening twice
shared bool
// seal system-level component
sys *appSealSys
// used in various sealing operations
internal.SystemConstants
// protected by upstream mutex
}
// Seal seals the app launch context
func (a *app) Seal(config *Config) error {
a.lock.Lock()
@ -176,10 +214,10 @@ func (a *app) Seal(config *Config) error {
seal.sys.bwrap.SetEnv = make(map[string]string)
}
// create wayland client wait channel if mediated wayland is enabled
// this channel being set enables mediated wayland setup later on
// create wayland struct and client wait channel if mediated wayland is enabled
// this field being set enables mediated wayland setup later on
if config.Confinement.Sandbox.Wayland {
seal.wlDone = make(chan struct{})
seal.wl = shim.NewWayland()
}
// open process state store

View File

@ -34,7 +34,7 @@ func (seal *appSeal) shareDisplay() error {
if wd, ok := os.LookupEnv(waylandDisplay); !ok {
return fmsg.WrapError(ErrWayland,
"WAYLAND_DISPLAY is not set")
} else if seal.wlDone == nil {
} else if seal.wl == nil {
// hardlink wayland socket
wp := path.Join(seal.RuntimePath, wd)
wpi := path.Join(seal.shareLocal, "wayland")
@ -46,8 +46,8 @@ func (seal *appSeal) shareDisplay() error {
// ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`)
seal.sys.UpdatePermType(system.EWayland, wp, acl.Read, acl.Write, acl.Execute)
} else {
// set wayland socket path (e.g. `/run/user/%d/wayland-%d`)
seal.wl = path.Join(seal.RuntimePath, wd)
// set wayland socket path for mediation (e.g. `/run/user/%d/wayland-%d`)
seal.wl.Path = path.Join(seal.RuntimePath, wd)
}
}

View File

@ -62,23 +62,19 @@ func (a *app) Start() error {
confSockPath := path.Join(a.seal.share, "shim")
a.cmd = exec.Command(a.seal.toolPath, commandBuilder(shim.EnvShim+"="+confSockPath)...)
a.cmd.Env = []string{}
a.cmd.Stdin = os.Stdin
a.cmd.Stdout = os.Stdout
a.cmd.Stderr = os.Stderr
a.cmd.Stdin, a.cmd.Stdout, a.cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
a.cmd.Dir = a.seal.RunDirPath
if wls, err := shim.ServeConfig(confSockPath, a.seal.sys.UID(), &shim.Payload{
if err := shim.ServeConfig(confSockPath, a.seal.sys.UID(), &shim.Payload{
Argv: a.seal.command,
Exec: shimExec,
Bwrap: a.seal.sys.bwrap,
WL: a.seal.wlDone != nil,
WL: a.seal.wl != nil,
Verbose: verbose.Get(),
}, a.seal.wl, a.seal.wlDone); err != nil {
}, a.seal.wl); err != nil {
return fmsg.WrapErrorSuffix(err,
"cannot listen on shim socket:")
} else {
a.wayland = wls
"cannot serve shim setup:")
}
// start shim
@ -185,9 +181,8 @@ func (a *app) Wait() (int, error) {
verbose.Println("process", strconv.Itoa(a.cmd.Process.Pid), "exited with exit code", r)
// close wayland connection
if a.wayland != nil {
close(a.seal.wlDone)
if err := a.wayland.Close(); err != nil {
if a.seal.wl != nil {
if err := a.seal.wl.Close(); err != nil {
fmt.Println("fortify: cannot close wayland connection:", err)
}
}

View File

@ -5,51 +5,9 @@ import (
"git.ophivana.moe/security/fortify/dbus"
"git.ophivana.moe/security/fortify/helper/bwrap"
"git.ophivana.moe/security/fortify/internal"
"git.ophivana.moe/security/fortify/internal/state"
"git.ophivana.moe/security/fortify/internal/system"
)
// appSeal seals the application with child-related information
type appSeal struct {
// wayland socket path if mediated wayland is enabled
wl string
// wait for wayland client to exit if mediated wayland is enabled,
// (wlDone == nil) determines whether mediated wayland setup is performed
wlDone chan struct{}
// app unique ID string representation
id string
// freedesktop application ID
fid string
// argv to start process with in the final confined environment
command []string
// persistent process state store
store state.Store
// uint8 representation of launch method sealed from config
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
// pass-through enablement tracking from config
et system.Enablements
// prevents sharing from happening twice
shared bool
// seal system-level component
sys *appSealSys
// used in various sealing operations
internal.SystemConstants
// protected by upstream mutex
}
// appSealSys encapsulates app seal behaviour with OS interactions
type appSealSys struct {
bwrap *bwrap.Config

View File

@ -14,19 +14,18 @@ import (
// called in the parent process
func ServeConfig(socket string, uid int, payload *Payload, wl string, done chan struct{}) (*net.UnixConn, error) {
var ws *net.UnixConn
func ServeConfig(socket string, uid int, payload *Payload, wl *Wayland) error {
if payload.WL {
if f, err := net.DialUnix("unix", nil, &net.UnixAddr{Name: wl, Net: "unix"}); err != nil {
return nil, err
if f, err := net.DialUnix("unix", nil, &net.UnixAddr{Name: wl.Path, Net: "unix"}); err != nil {
return err
} else {
verbose.Println("connected to wayland at", wl)
ws = f
wl.UnixConn = f
}
}
if c, err := net.ListenUnix("unix", &net.UnixAddr{Name: socket, Net: "unix"}); err != nil {
return nil, err
return err
} else {
verbose.Println("configuring shim on socket", socket)
if err = acl.UpdatePerm(socket, uid, acl.Read, acl.Write, acl.Execute); err != nil {
@ -47,7 +46,7 @@ func ServeConfig(socket string, uid int, payload *Payload, wl string, done chan
if payload.WL {
// get raw connection
var rc syscall.RawConn
if rc, err = ws.SyscallConn(); err != nil {
if rc, err = wl.SyscallConn(); err != nil {
fmt.Println("fortify: cannot obtain raw wayland connection:", err)
return
} else {
@ -61,7 +60,7 @@ func ServeConfig(socket string, uid int, payload *Payload, wl string, done chan
_ = conn.Close()
// block until shim exits
<-done
<-wl.done
verbose.Println("releasing wayland connection")
}); err != nil {
fmt.Println("fortify: cannot obtain wayland connection fd:", err)
@ -79,6 +78,6 @@ func ServeConfig(socket string, uid int, payload *Payload, wl string, done chan
fmt.Println("fortify: cannot remove dangling shim socket:", err)
}
}()
return ws, nil
return nil
}
}

35
internal/shim/wayland.go Normal file
View File

@ -0,0 +1,35 @@
package shim
import (
"net"
"sync"
)
// Wayland implements wayland mediation.
type Wayland struct {
// wayland socket path
Path string
// wayland connection
*net.UnixConn
connErr error
sync.Once
// wait for wayland client to exit
done chan struct{}
}
func (wl *Wayland) Close() error {
wl.Do(func() {
close(wl.done)
wl.connErr = wl.UnixConn.Close()
})
return wl.connErr
}
func NewWayland() *Wayland {
wl := new(Wayland)
wl.done = make(chan struct{})
return wl
}