From ff4a5a0bf8bbc838f330e747e201727bf8957750 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Wed, 19 Feb 2025 01:36:07 +0900 Subject: [PATCH] app: merge seal and sys The existence of the appSealSys struct was an implementation detail obsolete since system.I was integrated in 084cd84f36a1d608541b4aa4e1e95334395d8953. Signed-off-by: Ophestra --- internal/app/app.go | 4 +- internal/app/export_test.go | 2 +- internal/app/seal.go | 96 ++++++++++++++++++++++++------------- internal/app/share.go | 88 +++++++++++++++++----------------- internal/app/start.go | 22 ++++----- internal/app/system.go | 48 ------------------- 6 files changed, 122 insertions(+), 138 deletions(-) delete mode 100644 internal/app/system.go diff --git a/internal/app/app.go b/internal/app/app.go index 9dd082b..eb46392 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -47,10 +47,10 @@ func (a *app) String() string { } if a.appSeal != nil { - if a.appSeal.sys.user.uid == nil { + if a.appSeal.user.uid == nil { return fmt.Sprintf("(sealed app %s with invalid uid)", a.id) } - return fmt.Sprintf("(sealed app %s as uid %s)", a.id, a.appSeal.sys.user.uid) + return fmt.Sprintf("(sealed app %s as uid %s)", a.id, a.appSeal.user.uid) } return fmt.Sprintf("(unsealed app %s)", a.id) diff --git a/internal/app/export_test.go b/internal/app/export_test.go index 54b7f34..af52912 100644 --- a/internal/app/export_test.go +++ b/internal/app/export_test.go @@ -16,5 +16,5 @@ func NewWithID(id fst.ID, os sys.State) fst.App { func AppSystemBwrap(a fst.App) (*system.I, *bwrap.Config) { v := a.(*app) - return v.appSeal.sys.I, v.appSeal.sys.bwrap + return v.appSeal.sys, v.appSeal.container } diff --git a/internal/app/seal.go b/internal/app/seal.go index 9fce8f2..251ee2d 100644 --- a/internal/app/seal.go +++ b/internal/app/seal.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "io/fs" + "os" "path" "regexp" @@ -42,8 +43,13 @@ type appSeal struct { appID string // final argv, passed to init command []string - // state instance initialised during seal and used on process lifecycle events + + // state instance initialised during seal; used during process lifecycle events store state.Store + // whether [system.I] was committed; used during process lifecycle events + needRevert bool + // whether state was inserted into [state.Store]; used during process lifecycle events + stateInStore bool // process-specific share directory path ([os.TempDir]) share string @@ -57,13 +63,24 @@ type appSeal struct { // when this gets set no attempt is made to attach security-context-v1 // and the bare socket is mounted to the sandbox directWayland bool + // mount tmpfs over these paths, runs right before extraPerms + override []string // extra [acl.Update] ops, appended at the end of [system.I] extraPerms []*sealedExtraPerm + // post fsu state + user appUser + // inner XDG_RUNTIME_DIR, default formatting via user + innerRuntimeDir string + // mapped uid and gid in user namespace + mapuid *stringPair[int] + + sys *system.I + container *bwrap.Config + bwrapSync *os.File + // prevents sharing from happening twice shared bool - // seal system-level component - sys *appSealSys system.Enablements fst.Paths @@ -71,6 +88,24 @@ type appSeal struct { // protected by upstream mutex } +// appUser stores post-fsu credentials and metadata +type appUser struct { + // application id + aid *stringPair[int] + // target uid resolved by fid:aid + uid *stringPair[int] + + // supplementary group ids + supp []string + + // home directory host path + data string + // app user home directory + home string + // passwd database username + username string +} + type sealedExtraPerm struct { name string perms acl.Perms @@ -110,9 +145,6 @@ func (a *app) Seal(config *fst.Config) error { seal.appID = config.ID seal.command = config.Command - // create seal system component - seal.sys = new(appSealSys) - { // mapped uid defaults to 65534 to work around file ownership checks due to a bwrap limitation mapuid := 65534 @@ -121,8 +153,8 @@ func (a *app) Seal(config *fst.Config) error { // separate workaround is introduced to map priv-side caller uid in namespace mapuid = a.sys.Geteuid() } - seal.sys.mapuid = newInt(mapuid) - seal.sys.runtime = path.Join("/run/user", seal.sys.mapuid.String()) + seal.mapuid = newInt(mapuid) + seal.innerRuntimeDir = path.Join("/run/user", seal.mapuid.String()) } // validate uid and set user info @@ -130,42 +162,42 @@ func (a *app) Seal(config *fst.Config) error { return fmsg.WrapError(ErrUser, fmt.Sprintf("aid %d out of range", config.Confinement.AppID)) } - seal.sys.user = appUser{ + seal.user = appUser{ aid: newInt(config.Confinement.AppID), data: config.Confinement.Outer, home: config.Confinement.Inner, username: config.Confinement.Username, } - if seal.sys.user.username == "" { - seal.sys.user.username = "chronos" - } else if !posixUsername.MatchString(seal.sys.user.username) || - len(seal.sys.user.username) >= internal.Sysconf_SC_LOGIN_NAME_MAX() { + if seal.user.username == "" { + seal.user.username = "chronos" + } else if !posixUsername.MatchString(seal.user.username) || + len(seal.user.username) >= internal.Sysconf_SC_LOGIN_NAME_MAX() { return fmsg.WrapError(ErrName, - fmt.Sprintf("invalid user name %q", seal.sys.user.username)) + fmt.Sprintf("invalid user name %q", seal.user.username)) } - if seal.sys.user.data == "" || !path.IsAbs(seal.sys.user.data) { + if seal.user.data == "" || !path.IsAbs(seal.user.data) { return fmsg.WrapError(ErrHome, - fmt.Sprintf("invalid home directory %q", seal.sys.user.data)) + fmt.Sprintf("invalid home directory %q", seal.user.data)) } - if seal.sys.user.home == "" { - seal.sys.user.home = seal.sys.user.data + if seal.user.home == "" { + seal.user.home = seal.user.data } // invoke fsu for full uid - if u, err := a.sys.Uid(seal.sys.user.aid.unwrap()); err != nil { + if u, err := a.sys.Uid(seal.user.aid.unwrap()); err != nil { return err } else { - seal.sys.user.uid = newInt(u) + seal.user.uid = newInt(u) } // resolve supplementary group ids from names - seal.sys.user.supp = make([]string, len(config.Confinement.Groups)) + seal.user.supp = make([]string, len(config.Confinement.Groups)) for i, name := range config.Confinement.Groups { if g, err := a.sys.LookupGroup(name); err != nil { return fmsg.WrapError(err, fmt.Sprintf("unknown group %q", name)) } else { - seal.sys.user.supp[i] = g.Gid + seal.user.supp[i] = g.Gid } } @@ -242,11 +274,11 @@ func (a *app) Seal(config *fst.Config) error { if b, err := config.Confinement.Sandbox.Bwrap(a.sys); err != nil { return err } else { - seal.sys.bwrap = b + seal.container = b } - seal.sys.override = config.Confinement.Sandbox.Override - if seal.sys.bwrap.SetEnv == nil { - seal.sys.bwrap.SetEnv = make(map[string]string) + seal.override = config.Confinement.Sandbox.Override + if seal.container.SetEnv == nil { + seal.container.SetEnv = make(map[string]string) } // open process state store @@ -255,11 +287,11 @@ func (a *app) Seal(config *fst.Config) error { seal.store = state.NewMulti(seal.RunDirPath) // initialise system interface with os uid - seal.sys.I = system.New(seal.sys.user.uid.unwrap()) - seal.sys.I.IsVerbose = fmsg.Load - seal.sys.I.Verbose = fmsg.Verbose - seal.sys.I.Verbosef = fmsg.Verbosef - seal.sys.I.WrapErr = fmsg.WrapError + seal.sys = system.New(seal.user.uid.unwrap()) + seal.sys.IsVerbose = fmsg.Load + seal.sys.Verbose = fmsg.Verbose + seal.sys.Verbosef = fmsg.Verbosef + seal.sys.WrapErr = fmsg.WrapError // pass through enablements seal.Enablements = config.Confinement.Enablements @@ -271,7 +303,7 @@ func (a *app) Seal(config *fst.Config) error { // verbose log seal information fmsg.Verbosef("created application seal for uid %s (%s) groups: %v, command: %s", - seal.sys.user.uid, seal.sys.user.username, config.Confinement.Groups, config.Command) + seal.user.uid, seal.user.username, config.Confinement.Groups, config.Command) // seal app and release lock a.appSeal = seal diff --git a/internal/app/share.go b/internal/app/share.go index f6271b2..2094b48 100644 --- a/internal/app/share.go +++ b/internal/app/share.go @@ -68,23 +68,23 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os sys.State) error { seal.sys.UpdatePermType(system.User, targetTmpdirParent, acl.Execute) // ensure child tmpdir (e.g. `/tmp/fortify.%d/tmpdir/%d`) - targetTmpdir := path.Join(targetTmpdirParent, seal.sys.user.aid.String()) + targetTmpdir := path.Join(targetTmpdirParent, seal.user.aid.String()) seal.sys.Ensure(targetTmpdir, 01700) seal.sys.UpdatePermType(system.User, targetTmpdir, acl.Read, acl.Write, acl.Execute) - seal.sys.bwrap.Bind(targetTmpdir, "/tmp", false, true) + seal.container.Bind(targetTmpdir, "/tmp", false, true) /* XDG runtime directory */ // mount tmpfs on inner runtime (e.g. `/run/user/%d`) - seal.sys.bwrap.Tmpfs("/run/user", 1*1024*1024) - seal.sys.bwrap.Tmpfs(seal.sys.runtime, 8*1024*1024) + seal.container.Tmpfs("/run/user", 1*1024*1024) + seal.container.Tmpfs(seal.innerRuntimeDir, 8*1024*1024) // point to inner runtime path `/run/user/%d` - seal.sys.bwrap.SetEnv[xdgRuntimeDir] = seal.sys.runtime - seal.sys.bwrap.SetEnv[xdgSessionClass] = "user" - seal.sys.bwrap.SetEnv[xdgSessionType] = "tty" + seal.container.SetEnv[xdgRuntimeDir] = seal.innerRuntimeDir + seal.container.SetEnv[xdgSessionClass] = "user" + seal.container.SetEnv[xdgSessionType] = "tty" // ensure RunDir (e.g. `/run/user/%d/fortify`) seal.sys.Ensure(seal.RunDirPath, 0700) @@ -106,29 +106,29 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os sys.State) error { // look up shell sh := "/bin/sh" if s, ok := os.LookupEnv(shell); ok { - seal.sys.bwrap.SetEnv[shell] = s + seal.container.SetEnv[shell] = s sh = s } // bind home directory homeDir := "/var/empty" - if seal.sys.user.home != "" { - homeDir = seal.sys.user.home + if seal.user.home != "" { + homeDir = seal.user.home } username := "chronos" - if seal.sys.user.username != "" { - username = seal.sys.user.username + if seal.user.username != "" { + username = seal.user.username } - seal.sys.bwrap.Bind(seal.sys.user.data, homeDir, false, true) - seal.sys.bwrap.Chdir = homeDir - seal.sys.bwrap.SetEnv["HOME"] = homeDir - seal.sys.bwrap.SetEnv["USER"] = username + seal.container.Bind(seal.user.data, homeDir, false, true) + seal.container.Chdir = homeDir + seal.container.SetEnv["HOME"] = homeDir + seal.container.SetEnv["USER"] = username // generate /etc/passwd and /etc/group - seal.sys.bwrap.CopyBind("/etc/passwd", - []byte(username+":x:"+seal.sys.mapuid.String()+":"+seal.sys.mapuid.String()+":Fortify:"+homeDir+":"+sh+"\n")) - seal.sys.bwrap.CopyBind("/etc/group", - []byte("fortify:x:"+seal.sys.mapuid.String()+":\n")) + seal.container.CopyBind("/etc/passwd", + []byte(username+":x:"+seal.mapuid.String()+":"+seal.mapuid.String()+":Fortify:"+homeDir+":"+sh+"\n")) + seal.container.CopyBind("/etc/group", + []byte("fortify:x:"+seal.mapuid.String()+":\n")) /* Display servers @@ -136,7 +136,7 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os sys.State) error { // pass $TERM to launcher if t, ok := os.LookupEnv(term); ok { - seal.sys.bwrap.SetEnv[term] = t + seal.container.SetEnv[term] = t } // set up wayland @@ -151,8 +151,8 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os sys.State) error { socketPath = name } - innerPath := path.Join(seal.sys.runtime, wl.FallbackName) - seal.sys.bwrap.SetEnv[wl.WaylandDisplay] = wl.FallbackName + innerPath := path.Join(seal.innerRuntimeDir, wl.FallbackName) + seal.container.SetEnv[wl.WaylandDisplay] = wl.FallbackName if !seal.directWayland { // set up security-context-v1 socketDir := path.Join(seal.SharePath, "wayland") @@ -163,11 +163,11 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os sys.State) error { // use instance ID in case app id is not set appID = "uk.gensokyo.fortify." + seal.id } - seal.sys.Wayland(&seal.sys.sp, outerPath, socketPath, appID, seal.id) - seal.sys.bwrap.Bind(outerPath, innerPath) + seal.sys.Wayland(&seal.bwrapSync, outerPath, socketPath, appID, seal.id) + seal.container.Bind(outerPath, innerPath) } else { // bind mount wayland socket (insecure) fmsg.Verbose("direct wayland access, PROCEED WITH CAUTION") - seal.sys.bwrap.Bind(socketPath, innerPath) + seal.container.Bind(socketPath, innerPath) // ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`) seal.sys.UpdatePermType(system.EWayland, socketPath, acl.Read, acl.Write, acl.Execute) @@ -181,9 +181,9 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os sys.State) error { return fmsg.WrapError(ErrXDisplay, "DISPLAY is not set") } else { - seal.sys.ChangeHosts("#" + seal.sys.user.uid.String()) - seal.sys.bwrap.SetEnv[display] = d - seal.sys.bwrap.Bind("/tmp/.X11-unix", "/tmp/.X11-unix") + seal.sys.ChangeHosts("#" + seal.user.uid.String()) + seal.container.SetEnv[display] = d + seal.container.Bind("/tmp/.X11-unix", "/tmp/.X11-unix") } } @@ -221,10 +221,10 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os sys.State) error { // hard link pulse socket into target-executable share psi := path.Join(seal.shareLocal, "pulse") - p := path.Join(seal.sys.runtime, "pulse", "native") + p := path.Join(seal.innerRuntimeDir, "pulse", "native") seal.sys.Link(ps, psi) - seal.sys.bwrap.Bind(psi, p) - seal.sys.bwrap.SetEnv[pulseServer] = "unix:" + p + seal.container.Bind(psi, p) + seal.container.SetEnv[pulseServer] = "unix:" + p // publish current user's pulse cookie for target user if src, err := discoverPulseCookie(os); err != nil { @@ -232,9 +232,9 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os sys.State) error { fmsg.Verbose(strings.TrimSpace(err.(*fmsg.BaseError).Message())) } else { innerDst := fst.Tmp + "/pulse-cookie" - seal.sys.bwrap.SetEnv[pulseCookie] = innerDst + seal.container.SetEnv[pulseCookie] = innerDst payload := new([]byte) - seal.sys.bwrap.CopyBindRef(innerDst, &payload) + seal.container.CopyBindRef(innerDst, &payload) seal.sys.CopyFile(payload, src, 256, 256) } } @@ -260,14 +260,14 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os sys.State) error { } // share proxy sockets - sessionInner := path.Join(seal.sys.runtime, "bus") - seal.sys.bwrap.SetEnv[dbusSessionBusAddress] = "unix:path=" + sessionInner - seal.sys.bwrap.Bind(sessionPath, sessionInner) + sessionInner := path.Join(seal.innerRuntimeDir, "bus") + seal.container.SetEnv[dbusSessionBusAddress] = "unix:path=" + sessionInner + seal.container.Bind(sessionPath, sessionInner) seal.sys.UpdatePerm(sessionPath, acl.Read, acl.Write) if bus[1] != nil { systemInner := "/run/dbus/system_bus_socket" - seal.sys.bwrap.SetEnv[dbusSystemBusAddress] = "unix:path=" + systemInner - seal.sys.bwrap.Bind(systemPath, systemInner) + seal.container.SetEnv[dbusSystemBusAddress] = "unix:path=" + systemInner + seal.container.Bind(systemPath, systemInner) seal.sys.UpdatePerm(systemPath, acl.Read, acl.Write) } } @@ -276,14 +276,14 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os sys.State) error { Miscellaneous */ - // queue overriding tmpfs at the end of seal.sys.bwrap.Filesystem - for _, dest := range seal.sys.override { - seal.sys.bwrap.Tmpfs(dest, 8*1024) + // queue overriding tmpfs at the end of seal.container.Filesystem + for _, dest := range seal.override { + seal.container.Tmpfs(dest, 8*1024) } // mount fortify in sandbox for init - seal.sys.bwrap.Bind(os.MustExecutable(), path.Join(fst.Tmp, "sbin/fortify")) - seal.sys.bwrap.Symlink("fortify", path.Join(fst.Tmp, "sbin/init")) + seal.container.Bind(os.MustExecutable(), path.Join(fst.Tmp, "sbin/fortify")) + seal.container.Symlink("fortify", path.Join(fst.Tmp, "sbin/init")) // append extra perms for _, p := range seal.extraPerms { diff --git a/internal/app/start.go b/internal/app/start.go index f5fe9a3..4cbe616 100644 --- a/internal/app/start.go +++ b/internal/app/start.go @@ -51,15 +51,15 @@ func (a *app) Run(ctx context.Context, rs *fst.RunState) error { if err := a.appSeal.sys.Commit(ctx); err != nil { return err } - a.appSeal.sys.needRevert = true + a.appSeal.needRevert = true // start shim via manager a.shim = new(shim.Shim) waitErr := make(chan error, 1) if startTime, err := a.shim.Start( - a.appSeal.sys.user.aid.String(), - a.appSeal.sys.user.supp, - a.appSeal.sys.sp, + a.appSeal.user.aid.String(), + a.appSeal.user.supp, + a.appSeal.bwrapSync, ); err != nil { return err } else { @@ -80,8 +80,8 @@ func (a *app) Run(ctx context.Context, rs *fst.RunState) error { if err = a.shim.Serve(shimSetupCtx, &shim.Payload{ Argv: a.appSeal.command, Exec: shimExec, - Bwrap: a.appSeal.sys.bwrap, - Home: a.appSeal.sys.user.data, + Bwrap: a.appSeal.container, + Home: a.appSeal.user.data, Verbose: fmsg.Load(), }); err != nil { @@ -97,10 +97,10 @@ func (a *app) Run(ctx context.Context, rs *fst.RunState) error { // register process state var err0 = new(StateStoreError) - err0.Inner, err0.DoErr = a.appSeal.store.Do(a.appSeal.sys.user.aid.unwrap(), func(c state.Cursor) { + err0.Inner, err0.DoErr = a.appSeal.store.Do(a.appSeal.user.aid.unwrap(), func(c state.Cursor) { err0.InnerErr = c.Save(&sd, a.appSeal.ct) }) - a.appSeal.sys.saveState = true + a.appSeal.stateInStore = true if err = err0.equiv("cannot save process state:"); err != nil { return err } @@ -147,10 +147,10 @@ func (a *app) Run(ctx context.Context, rs *fst.RunState) error { // update store and revert app setup transaction e := new(StateStoreError) - e.Inner, e.DoErr = a.appSeal.store.Do(a.appSeal.sys.user.aid.unwrap(), func(b state.Cursor) { + e.Inner, e.DoErr = a.appSeal.store.Do(a.appSeal.user.aid.unwrap(), func(b state.Cursor) { e.InnerErr = func() error { // destroy defunct state entry - if cmd := a.shim.Unwrap(); cmd != nil && a.appSeal.sys.saveState { + if cmd := a.shim.Unwrap(); cmd != nil && a.appSeal.stateInStore { if err := b.Destroy(a.id.unwrap()); err != nil { return err } @@ -198,7 +198,7 @@ func (a *app) Run(ctx context.Context, rs *fst.RunState) error { } } - if a.appSeal.sys.needRevert { + if a.appSeal.needRevert { if err := a.appSeal.sys.Revert(ec); err != nil { return err.(RevertCompoundError) } diff --git a/internal/app/system.go b/internal/app/system.go deleted file mode 100644 index 3a92b0f..0000000 --- a/internal/app/system.go +++ /dev/null @@ -1,48 +0,0 @@ -package app - -import ( - "os" - - "git.gensokyo.uk/security/fortify/helper/bwrap" - "git.gensokyo.uk/security/fortify/system" -) - -// appSealSys encapsulates app seal behaviour with OS interactions -type appSealSys struct { - bwrap *bwrap.Config - // bwrap sync fd - sp *os.File - // paths to override by mounting tmpfs over them - override []string - - // default formatted XDG_RUNTIME_DIR of User - runtime string - // target user sealed from config - user appUser - - // mapped uid and gid in user namespace - mapuid *stringPair[int] - - needRevert bool - saveState bool - *system.I - - // protected by upstream mutex -} - -type appUser struct { - // application id - aid *stringPair[int] - // target uid resolved by fid:aid - uid *stringPair[int] - - // supplementary group ids - supp []string - - // home directory host path - data string - // app user home directory - home string - // passwd database username - username string -}