app: move wayland mediation to shim package
test / test (push) Successful in 41s Details

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 Umiker 2024-10-20 22:54:47 +09:00
parent 133f23e0de
commit 362ee73893
Signed by: cat
SSH Key Fingerprint: SHA256:vQhTOP4tHcsFb0365dxe6HJBKpv7PZ0KZNFx2AjBnRI
7 changed files with 94 additions and 73 deletions

View File

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

View File

@ -2,6 +2,7 @@ package app
import ( import (
"errors" "errors"
"git.ophivana.moe/security/fortify/internal/shim"
"os" "os"
"os/exec" "os/exec"
"os/user" "os/user"
@ -36,6 +37,43 @@ var (
ErrMachineCtl = errors.New("machinectl not available") 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 // Seal seals the app launch context
func (a *app) Seal(config *Config) error { func (a *app) Seal(config *Config) error {
a.lock.Lock() a.lock.Lock()
@ -176,10 +214,10 @@ func (a *app) Seal(config *Config) error {
seal.sys.bwrap.SetEnv = make(map[string]string) seal.sys.bwrap.SetEnv = make(map[string]string)
} }
// create wayland client wait channel if mediated wayland is enabled // create wayland struct and client wait channel if mediated wayland is enabled
// this channel being set enables mediated wayland setup later on // this field being set enables mediated wayland setup later on
if config.Confinement.Sandbox.Wayland { if config.Confinement.Sandbox.Wayland {
seal.wlDone = make(chan struct{}) seal.wl = shim.NewWayland()
} }
// open process state store // open process state store

View File

@ -34,7 +34,7 @@ func (seal *appSeal) shareDisplay() error {
if wd, ok := os.LookupEnv(waylandDisplay); !ok { if wd, ok := os.LookupEnv(waylandDisplay); !ok {
return fmsg.WrapError(ErrWayland, return fmsg.WrapError(ErrWayland,
"WAYLAND_DISPLAY is not set") "WAYLAND_DISPLAY is not set")
} else if seal.wlDone == nil { } else if seal.wl == nil {
// hardlink wayland socket // hardlink wayland socket
wp := path.Join(seal.RuntimePath, wd) wp := path.Join(seal.RuntimePath, wd)
wpi := path.Join(seal.shareLocal, "wayland") 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`) // ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`)
seal.sys.UpdatePermType(system.EWayland, wp, acl.Read, acl.Write, acl.Execute) seal.sys.UpdatePermType(system.EWayland, wp, acl.Read, acl.Write, acl.Execute)
} else { } else {
// set wayland socket path (e.g. `/run/user/%d/wayland-%d`) // set wayland socket path for mediation (e.g. `/run/user/%d/wayland-%d`)
seal.wl = path.Join(seal.RuntimePath, wd) 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") confSockPath := path.Join(a.seal.share, "shim")
a.cmd = exec.Command(a.seal.toolPath, commandBuilder(shim.EnvShim+"="+confSockPath)...) a.cmd = exec.Command(a.seal.toolPath, commandBuilder(shim.EnvShim+"="+confSockPath)...)
a.cmd.Env = []string{} a.cmd.Env = []string{}
a.cmd.Stdin = os.Stdin a.cmd.Stdin, a.cmd.Stdout, a.cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
a.cmd.Stdout = os.Stdout
a.cmd.Stderr = os.Stderr
a.cmd.Dir = a.seal.RunDirPath 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, Argv: a.seal.command,
Exec: shimExec, Exec: shimExec,
Bwrap: a.seal.sys.bwrap, Bwrap: a.seal.sys.bwrap,
WL: a.seal.wlDone != nil, WL: a.seal.wl != nil,
Verbose: verbose.Get(), Verbose: verbose.Get(),
}, a.seal.wl, a.seal.wlDone); err != nil { }, a.seal.wl); err != nil {
return fmsg.WrapErrorSuffix(err, return fmsg.WrapErrorSuffix(err,
"cannot listen on shim socket:") "cannot serve shim setup:")
} else {
a.wayland = wls
} }
// start shim // 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) verbose.Println("process", strconv.Itoa(a.cmd.Process.Pid), "exited with exit code", r)
// close wayland connection // close wayland connection
if a.wayland != nil { if a.seal.wl != nil {
close(a.seal.wlDone) if err := a.seal.wl.Close(); err != nil {
if err := a.wayland.Close(); err != nil {
fmt.Println("fortify: cannot close wayland connection:", err) 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/dbus"
"git.ophivana.moe/security/fortify/helper/bwrap" "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" "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 // appSealSys encapsulates app seal behaviour with OS interactions
type appSealSys struct { type appSealSys struct {
bwrap *bwrap.Config bwrap *bwrap.Config

View File

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