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..7de6aec 100644 --- a/internal/app/seal.go +++ b/internal/app/seal.go @@ -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 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 +}