From 362ee73893c2c67480ae9b62f69f420f54f42bec Mon Sep 17 00:00:00 2001 From: Ophestra Umiker Date: Sun, 20 Oct 2024 22:54:47 +0900 Subject: [PATCH] app: move wayland mediation to shim package 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 --- internal/app/app.go | 4 ---- internal/app/seal.go | 44 ++++++++++++++++++++++++++++++++--- internal/app/share.display.go | 6 ++--- internal/app/start.go | 19 ++++++--------- internal/app/system.go | 42 --------------------------------- internal/shim/parent.go | 17 +++++++------- internal/shim/wayland.go | 35 ++++++++++++++++++++++++++++ 7 files changed, 94 insertions(+), 73 deletions(-) create mode 100644 internal/shim/wayland.go diff --git a/internal/app/app.go b/internal/app/app.go index 4c3a60c..5c5e345 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -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 diff --git a/internal/app/seal.go b/internal/app/seal.go index d4b4efa..82fc36e 100644 --- a/internal/app/seal.go +++ b/internal/app/seal.go @@ -2,6 +2,7 @@ package app import ( "errors" + "git.ophivana.moe/security/fortify/internal/shim" "os" "os/exec" "os/user" @@ -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 diff --git a/internal/app/share.display.go b/internal/app/share.display.go index 7c50b63..18fad7a 100644 --- a/internal/app/share.display.go +++ b/internal/app/share.display.go @@ -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) } } diff --git a/internal/app/start.go b/internal/app/start.go index 6d30102..3106a1e 100644 --- a/internal/app/start.go +++ b/internal/app/start.go @@ -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) } } diff --git a/internal/app/system.go b/internal/app/system.go index 834bdc9..2fc413e 100644 --- a/internal/app/system.go +++ b/internal/app/system.go @@ -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 diff --git a/internal/shim/parent.go b/internal/shim/parent.go index 9382db2..0e90383 100644 --- a/internal/shim/parent.go +++ b/internal/shim/parent.go @@ -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 } } diff --git a/internal/shim/wayland.go b/internal/shim/wayland.go new file mode 100644 index 0000000..05f0427 --- /dev/null +++ b/internal/shim/wayland.go @@ -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 +}