From b3ef53b193bdf764d8f04e19ea47901b71eec10b Mon Sep 17 00:00:00 2001 From: Ophestra Umiker Date: Fri, 6 Dec 2024 04:25:33 +0900 Subject: [PATCH] app: integrate security-context-v1 Should be able to get rid of XDG_RUNTIME_DIR share after this. Signed-off-by: Ophestra Umiker --- cmd/finit/ipc/payload.go | 2 - cmd/finit/main.go | 8 ---- cmd/fshim/ipc/payload.go | 13 ++---- cmd/fshim/ipc/shim/shim.go | 18 +++++--- cmd/fshim/ipc/wayland.go | 75 ---------------------------------- cmd/fshim/main.go | 49 ++-------------------- internal/app/app_nixos_test.go | 4 +- internal/app/app_pd_test.go | 6 +-- internal/app/config.go | 18 ++++---- internal/app/seal.go | 12 ++---- internal/app/share.display.go | 25 +++++++++--- internal/app/start.go | 12 ++---- 12 files changed, 57 insertions(+), 185 deletions(-) delete mode 100644 cmd/fshim/ipc/wayland.go diff --git a/cmd/finit/ipc/payload.go b/cmd/finit/ipc/payload.go index 535a0e7..d1dc9ec 100644 --- a/cmd/finit/ipc/payload.go +++ b/cmd/finit/ipc/payload.go @@ -7,8 +7,6 @@ type Payload struct { Argv0 string // child full argv Argv []string - // wayland fd, -1 to disable - WL int // verbosity pass through Verbose bool diff --git a/cmd/finit/main.go b/cmd/finit/main.go index da3eaea..b9744e0 100644 --- a/cmd/finit/main.go +++ b/cmd/finit/main.go @@ -92,14 +92,6 @@ func main() { cmd.Args = payload.Argv cmd.Env = os.Environ() - // pass wayland fd - if payload.WL != -1 { - if f := os.NewFile(uintptr(payload.WL), "wayland"); f != nil { - cmd.Env = append(cmd.Env, "WAYLAND_SOCKET="+strconv.Itoa(3+len(cmd.ExtraFiles))) - cmd.ExtraFiles = append(cmd.ExtraFiles, f) - } - } - if err := cmd.Start(); err != nil { fmsg.Fatalf("cannot start %q: %v", payload.Argv0, err) } diff --git a/cmd/fshim/ipc/payload.go b/cmd/fshim/ipc/payload.go index 1aaddb1..f6b0c92 100644 --- a/cmd/fshim/ipc/payload.go +++ b/cmd/fshim/ipc/payload.go @@ -2,7 +2,6 @@ package shim0 import ( "encoding/gob" - "errors" "net" "git.ophivana.moe/security/fortify/helper/bwrap" @@ -18,25 +17,19 @@ type Payload struct { Exec [2]string // bwrap config Bwrap *bwrap.Config - // whether to pass wayland fd - WL bool + // sync fd + Sync *uintptr // verbosity pass through Verbose bool } -func (p *Payload) Serve(conn *net.UnixConn, wl *Wayland) error { +func (p *Payload) Serve(conn *net.UnixConn) error { if err := gob.NewEncoder(conn).Encode(*p); err != nil { return fmsg.WrapErrorSuffix(err, "cannot stream shim payload:") } - if wl != nil { - if err := wl.WriteUnix(conn); err != nil { - return errors.Join(err, conn.Close()) - } - } - return fmsg.WrapErrorSuffix(conn.Close(), "cannot close setup connection:") } diff --git a/cmd/fshim/ipc/shim/shim.go b/cmd/fshim/ipc/shim/shim.go index db4a9b0..d01d02e 100644 --- a/cmd/fshim/ipc/shim/shim.go +++ b/cmd/fshim/ipc/shim/shim.go @@ -39,14 +39,12 @@ type Shim struct { abortOnce sync.Once // fallback exit notifier with error returned killing the process killFallback chan error - // wayland mediation, nil if disabled - wl *shim0.Wayland // shim setup payload payload *shim0.Payload } -func New(uid uint32, aid string, supp []string, socket string, wl *shim0.Wayland, payload *shim0.Payload) *Shim { - return &Shim{uid: uid, aid: aid, supp: supp, socket: socket, wl: wl, payload: payload} +func New(uid uint32, aid string, supp []string, socket string, payload *shim0.Payload) *Shim { + return &Shim{uid: uid, aid: aid, supp: supp, socket: socket, payload: payload} } func (s *Shim) String() string { @@ -112,6 +110,14 @@ func (s *Shim) Start() (*time.Time, error) { } s.cmd.Stdin, s.cmd.Stdout, s.cmd.Stderr = os.Stdin, os.Stdout, os.Stderr s.cmd.Dir = "/" + + // pass sync fd if set + if s.payload.Bwrap.Sync() != nil { + fd := uintptr(3 + len(s.cmd.ExtraFiles)) + s.payload.Sync = &fd + s.cmd.ExtraFiles = append(s.cmd.ExtraFiles, s.payload.Bwrap.Sync()) + } + fmsg.VPrintln("starting shim via fsu:", s.cmd) fmsg.Suspend() // withhold messages to stderr if err := s.cmd.Start(); err != nil { @@ -172,9 +178,9 @@ func (s *Shim) Start() (*time.Time, error) { return &startTime, err } - // serve payload and wayland fd if enabled + // serve payload // this also closes the connection - err := s.payload.Serve(conn, s.wl) + err := s.payload.Serve(conn) if err == nil { killShim = func() {} } diff --git a/cmd/fshim/ipc/wayland.go b/cmd/fshim/ipc/wayland.go deleted file mode 100644 index 132e74f..0000000 --- a/cmd/fshim/ipc/wayland.go +++ /dev/null @@ -1,75 +0,0 @@ -package shim0 - -import ( - "fmt" - "net" - "sync" - "syscall" - - "git.ophivana.moe/security/fortify/internal/fmsg" -) - -// Wayland implements wayland mediation. -type Wayland struct { - // wayland socket path - Path string - - // wayland connection - conn *net.UnixConn - - connErr error - sync.Once - // wait for wayland client to exit - done chan struct{} -} - -func (wl *Wayland) WriteUnix(conn *net.UnixConn) error { - // connect to host wayland socket - if f, err := net.DialUnix("unix", nil, &net.UnixAddr{Name: wl.Path, Net: "unix"}); err != nil { - return fmsg.WrapErrorSuffix(err, - fmt.Sprintf("cannot connect to wayland at %q:", wl.Path)) - } else { - fmsg.VPrintf("connected to wayland at %q", wl.Path) - wl.conn = f - } - - // set up for passing wayland socket - if rc, err := wl.conn.SyscallConn(); err != nil { - return fmsg.WrapErrorSuffix(err, "cannot obtain raw wayland connection:") - } else { - ec := make(chan error) - go func() { - // pass wayland connection fd - if err = rc.Control(func(fd uintptr) { - if _, _, err = conn.WriteMsgUnix(nil, syscall.UnixRights(int(fd)), nil); err != nil { - ec <- fmsg.WrapErrorSuffix(err, "cannot pass wayland connection to shim:") - return - } - ec <- nil - - // block until shim exits - <-wl.done - fmsg.VPrintln("releasing wayland connection") - }); err != nil { - ec <- fmsg.WrapErrorSuffix(err, "cannot obtain wayland connection fd:") - return - } - }() - return <-ec - } -} - -func (wl *Wayland) Close() error { - wl.Do(func() { - close(wl.done) - wl.connErr = wl.conn.Close() - }) - - return wl.connErr -} - -func NewWayland() *Wayland { - wl := new(Wayland) - wl.done = make(chan struct{}) - return wl -} diff --git a/cmd/fshim/main.go b/cmd/fshim/main.go index 2322984..222419e 100644 --- a/cmd/fshim/main.go +++ b/cmd/fshim/main.go @@ -2,7 +2,6 @@ package main import ( "encoding/gob" - "errors" "net" "os" "path" @@ -76,14 +75,9 @@ func main() { fmsg.Fatal("bwrap config not supplied") } - // receive wayland fd over socket - wfd := -1 - if payload.WL { - if fd, err := receiveWLfd(conn); err != nil { - fmsg.Fatalf("cannot receive wayland fd: %v", err) - } else { - wfd = fd - } + // restore bwrap sync fd + if payload.Sync != nil { + payload.Bwrap.SetSync(os.NewFile(*payload.Sync, "sync")) } // close setup socket @@ -116,16 +110,6 @@ func main() { var extraFiles []*os.File - // pass wayland fd - if wfd != -1 { - if f := os.NewFile(uintptr(wfd), "wayland"); f != nil { - ic.WL = 3 + len(extraFiles) - extraFiles = append(extraFiles, f) - } - } else { - ic.WL = -1 - } - // share config pipe if r, w, err := os.Pipe(); err != nil { fmsg.Fatalf("cannot pipe: %v", err) @@ -168,30 +152,3 @@ func main() { } } } - -func receiveWLfd(conn *net.UnixConn) (int, error) { - oob := make([]byte, syscall.CmsgSpace(4)) // single fd - - if _, oobn, _, _, err := conn.ReadMsgUnix(nil, oob); err != nil { - return -1, err - } else if len(oob) != oobn { - return -1, errors.New("invalid message length") - } - - var msg syscall.SocketControlMessage - if messages, err := syscall.ParseSocketControlMessage(oob); err != nil { - return -1, err - } else if len(messages) != 1 { - return -1, errors.New("unexpected message count") - } else { - msg = messages[0] - } - - if fds, err := syscall.ParseUnixRights(&msg); err != nil { - return -1, err - } else if len(fds) != 1 { - return -1, errors.New("unexpected fd count") - } else { - return fds[0], nil - } -} diff --git a/internal/app/app_nixos_test.go b/internal/app/app_nixos_test.go index 06bde05..3338079 100644 --- a/internal/app/app_nixos_test.go +++ b/internal/app/app_nixos_test.go @@ -10,7 +10,7 @@ import ( var testCasesNixos = []sealTestCase{ { - "nixos chromium", new(stubNixOS), + "nixos chromium direct wayland", new(stubNixOS), &app.Config{ ID: "org.chromium.Chromium", Command: []string{"/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"}, @@ -18,7 +18,7 @@ var testCasesNixos = []sealTestCase{ AppID: 1, Groups: []string{}, Username: "u0_a1", Outer: "/var/lib/persist/module/fortify/0/1", Sandbox: &app.SandboxConfig{ - UserNS: true, Net: true, MapRealUID: true, Env: nil, + UserNS: true, Net: true, MapRealUID: true, DirectWayland: true, Env: nil, Filesystem: []*app.FilesystemConfig{ {Src: "/bin", Must: true}, {Src: "/usr/bin", Must: true}, {Src: "/nix/store", Must: true}, {Src: "/run/current-system", Must: true}, diff --git a/internal/app/app_pd_test.go b/internal/app/app_pd_test.go index 224d263..a97bbcf 100644 --- a/internal/app/app_pd_test.go +++ b/internal/app/app_pd_test.go @@ -248,8 +248,8 @@ var testCasesPd = []sealTestCase{ Ephemeral(system.Process, "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c", acl.Execute). WriteType(system.Process, "/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/passwd", "chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n"). WriteType(system.Process, "/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/group", "fortify:x:65534:\n"). - Link("/run/user/1971/wayland-0", "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/wayland"). - UpdatePermType(system.EWayland, "/run/user/1971/wayland-0", acl.Read, acl.Write, acl.Execute). + Ensure("/tmp/fortify.1971/wayland", 0711). + Wayland("/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/1971/wayland-0", "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"). Link("/run/user/1971/pulse/native", "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse"). CopyFile("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie"). MustProxyDBus("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", &dbus.Config{ @@ -442,7 +442,7 @@ var testCasesPd = []sealTestCase{ Bind("/home/chronos", "/home/chronos", false, true). Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/passwd", "/etc/passwd"). Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/group", "/etc/group"). - Bind("/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/wayland", "/run/user/65534/wayland-0"). + Bind("/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/65534/wayland-0"). Bind("/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse", "/run/user/65534/pulse/native"). Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/pulse-cookie", "/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/pulse-cookie"). Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", "/run/user/65534/bus"). diff --git a/internal/app/config.go b/internal/app/config.go index 298925d..758dcd0 100644 --- a/internal/app/config.go +++ b/internal/app/config.go @@ -62,8 +62,8 @@ type SandboxConfig struct { NoNewSession bool `json:"no_new_session,omitempty"` // map target user uid to privileged user uid in the user namespace MapRealUID bool `json:"map_real_uid"` - // mediated access to wayland socket - Wayland bool `json:"wayland,omitempty"` + // direct access to wayland socket + DirectWayland bool `json:"direct_wayland,omitempty"` // final environment variables Env map[string]string `json:"env"` @@ -190,13 +190,13 @@ func Template() *Config { Outer: "/var/lib/persist/home/org.chromium.Chromium", Inner: "/var/lib/fortify", Sandbox: &SandboxConfig{ - Hostname: "localhost", - UserNS: true, - Net: true, - NoNewSession: true, - MapRealUID: true, - Dev: true, - Wayland: false, + Hostname: "localhost", + UserNS: true, + Net: true, + NoNewSession: true, + MapRealUID: true, + Dev: true, + DirectWayland: false, // example API credentials pulled from Google Chrome // DO NOT USE THESE IN A REAL BROWSER Env: map[string]string{ diff --git a/internal/app/seal.go b/internal/app/seal.go index d3c7a87..94c9e06 100644 --- a/internal/app/seal.go +++ b/internal/app/seal.go @@ -8,7 +8,6 @@ import ( "regexp" "strconv" - shim "git.ophivana.moe/security/fortify/cmd/fshim/ipc" "git.ophivana.moe/security/fortify/dbus" "git.ophivana.moe/security/fortify/internal/fmsg" "git.ophivana.moe/security/fortify/internal/linux" @@ -29,8 +28,6 @@ var posixUsername = regexp.MustCompilePOSIX("^[a-z_]([A-Za-z0-9_-]{0,31}|[A-Za-z type appSeal struct { // app unique ID string representation id string - // wayland mediation, disabled if nil - wl *shim.Wayland // dbus proxy message buffer retriever dbusMsg func(f func(msgbuf []string)) @@ -48,6 +45,8 @@ type appSeal struct { // pass-through enablement tracking from config et system.Enablements + // wayland socket direct access + directWayland bool // prevents sharing from happening twice shared bool @@ -204,6 +203,7 @@ func (a *app) Seal(config *Config) error { config.Confinement.Sandbox = conf } + seal.directWayland = config.Confinement.Sandbox.DirectWayland if b, err := config.Confinement.Sandbox.Bwrap(a.os); err != nil { return err } else { @@ -214,12 +214,6 @@ func (a *app) Seal(config *Config) error { seal.sys.bwrap.SetEnv = make(map[string]string) } - // 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.wl = shim.NewWayland() - } - // open process state store // the simple store only starts holding an open file after first action // store activity begins after Start is called and must end before Wait diff --git a/internal/app/share.display.go b/internal/app/share.display.go index 801ca54..b1d9834 100644 --- a/internal/app/share.display.go +++ b/internal/app/share.display.go @@ -31,23 +31,36 @@ func (seal *appSeal) shareDisplay(os linux.System) error { // set up wayland if seal.et.Has(system.EWayland) { + var wp string if wd, ok := os.LookupEnv(waylandDisplay); !ok { return fmsg.WrapError(ErrWayland, "WAYLAND_DISPLAY is not set") - } else if seal.wl == nil { + } else { + wp = path.Join(seal.RuntimePath, wd) + } + + w := path.Join(seal.sys.runtime, "wayland-0") + seal.sys.bwrap.SetEnv[waylandDisplay] = w + + if seal.directWayland { // hardlink wayland socket - wp := path.Join(seal.RuntimePath, wd) wpi := path.Join(seal.shareLocal, "wayland") - w := path.Join(seal.sys.runtime, "wayland-0") seal.sys.Link(wp, wpi) - seal.sys.bwrap.SetEnv[waylandDisplay] = w seal.sys.bwrap.Bind(wpi, w) // 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 for mediation (e.g. `/run/user/%d/wayland-%d`) - seal.wl.Path = path.Join(seal.RuntimePath, wd) + wc := path.Join(seal.SharePath, "wayland") + wt := path.Join(wc, seal.id) + seal.sys.Ensure(wc, 0711) + appID := seal.fid + if appID == "" { + // use instance ID in case app id is not set + appID = "moe.ophivana.fortify." + seal.id + } + seal.sys.Wayland(wt, wp, appID, seal.id) + seal.sys.bwrap.Bind(wt, w) } } diff --git a/internal/app/start.go b/internal/app/start.go index c33d26f..e1247d4 100644 --- a/internal/app/start.go +++ b/internal/app/start.go @@ -47,12 +47,10 @@ func (a *app) Start() error { a.seal.sys.user.as, a.seal.sys.user.supp, path.Join(a.seal.share, "shim"), - a.seal.wl, &shim0.Payload{ Argv: a.seal.command, Exec: shimExec, Bwrap: a.seal.sys.bwrap, - WL: a.seal.wl != nil, Verbose: fmsg.Verbose(), }, @@ -64,6 +62,9 @@ func (a *app) Start() error { } a.seal.sys.needRevert = true + // export sync pipe from sys + a.seal.sys.bwrap.SetSync(a.seal.sys.Sync()) + if startTime, err := a.shim.Start(); err != nil { return err } else { @@ -199,13 +200,6 @@ func (a *app) Wait() (int, error) { }) } - // close wayland connection - if a.seal.wl != nil { - if err := a.seal.wl.Close(); err != nil { - fmsg.Println("cannot close wayland connection:", err) - } - } - // update store and revert app setup transaction e := new(StateStoreError) e.Inner, e.DoErr = a.seal.store.Do(func(b state.Backend) {