app: store values with string representation
All checks were successful
Test / Create distribution (push) Successful in 25s
Test / Run NixOS test (push) Successful in 3m26s

Improves code readability without changing memory layout.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-02-19 00:25:00 +09:00
parent 648e1d641a
commit a748d40745
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
7 changed files with 64 additions and 46 deletions

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"fmt"
"sync" "sync"
"git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/fst"
@ -10,14 +11,18 @@ import (
func New(os sys.State) (fst.App, error) { func New(os sys.State) (fst.App, error) {
a := new(app) a := new(app)
a.id = new(fst.ID)
a.os = os a.os = os
return a, fst.NewAppID(a.id)
id := new(fst.ID)
err := fst.NewAppID(id)
a.id = newID(id)
return a, err
} }
type app struct { type app struct {
// application unique identifier // application unique identifier
id *fst.ID id *stringPair[fst.ID]
// operating system interface // operating system interface
os sys.State os sys.State
// shim process manager // shim process manager
@ -28,13 +33,11 @@ type app struct {
lock sync.RWMutex lock sync.RWMutex
} }
func (a *app) ID() fst.ID { func (a *app) ID() fst.ID { return a.id.unwrap() }
return *a.id
}
func (a *app) String() string { func (a *app) String() string {
if a == nil { if a == nil {
return "(invalid fortified app)" return "(invalid app)"
} }
a.lock.RLock() a.lock.RLock()
@ -45,8 +48,11 @@ func (a *app) String() string {
} }
if a.seal != nil { if a.seal != nil {
return "(sealed fortified app as uid " + a.seal.sys.user.us + ")" if a.seal.sys.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.seal.sys.user.uid)
} }
return "(unsealed fortified app)" return fmt.Sprintf("(unsealed app %s)", a.id)
} }

View File

@ -9,7 +9,7 @@ import (
func NewWithID(id fst.ID, os sys.State) fst.App { func NewWithID(id fst.ID, os sys.State) fst.App {
a := new(app) a := new(app)
a.id = &id a.id = newID(&id)
a.os = os a.os = os
return a return a
} }

View File

@ -9,7 +9,6 @@ import (
"io/fs" "io/fs"
"path" "path"
"regexp" "regexp"
"strconv"
"git.gensokyo.uk/security/fortify/acl" "git.gensokyo.uk/security/fortify/acl"
"git.gensokyo.uk/security/fortify/dbus" "git.gensokyo.uk/security/fortify/dbus"
@ -110,14 +109,17 @@ func (a *app) Seal(config *fst.Config) error {
// create seal system component // create seal system component
seal.sys = new(appSealSys) seal.sys = new(appSealSys)
// mapped uid {
// mapped uid defaults to 65534 to work around file ownership checks due to a bwrap limitation
mapuid := 65534
if config.Confinement.Sandbox != nil && config.Confinement.Sandbox.MapRealUID { if config.Confinement.Sandbox != nil && config.Confinement.Sandbox.MapRealUID {
seal.sys.mappedID = a.os.Geteuid() // some programs fail to connect to dbus session running as a different uid, so a
} else { // separate workaround is introduced to map priv-side caller uid in namespace
seal.sys.mappedID = 65534 mapuid = a.os.Geteuid()
}
seal.sys.mapuid = newInt(mapuid)
seal.sys.runtime = path.Join("/run/user", seal.sys.mapuid.String())
} }
seal.sys.mappedIDString = strconv.Itoa(seal.sys.mappedID)
seal.sys.runtime = path.Join("/run/user", seal.sys.mappedIDString)
// validate uid and set user info // validate uid and set user info
if config.Confinement.AppID < 0 || config.Confinement.AppID > 9999 { if config.Confinement.AppID < 0 || config.Confinement.AppID > 9999 {
@ -125,8 +127,7 @@ func (a *app) Seal(config *fst.Config) error {
fmt.Sprintf("aid %d out of range", config.Confinement.AppID)) fmt.Sprintf("aid %d out of range", config.Confinement.AppID))
} }
seal.sys.user = appUser{ seal.sys.user = appUser{
aid: config.Confinement.AppID, aid: newInt(config.Confinement.AppID),
as: strconv.Itoa(config.Confinement.AppID),
data: config.Confinement.Outer, data: config.Confinement.Outer,
home: config.Confinement.Inner, home: config.Confinement.Inner,
username: config.Confinement.Username, username: config.Confinement.Username,
@ -147,11 +148,10 @@ func (a *app) Seal(config *fst.Config) error {
} }
// invoke fsu for full uid // invoke fsu for full uid
if u, err := a.os.Uid(seal.sys.user.aid); err != nil { if u, err := a.os.Uid(seal.sys.user.aid.unwrap()); err != nil {
return err return err
} else { } else {
seal.sys.user.uid = u seal.sys.user.uid = newInt(u)
seal.sys.user.us = strconv.Itoa(u)
} }
// resolve supplementary group ids from names // resolve supplementary group ids from names
@ -251,7 +251,7 @@ func (a *app) Seal(config *fst.Config) error {
seal.store = state.NewMulti(seal.RunDirPath) seal.store = state.NewMulti(seal.RunDirPath)
// initialise system interface with os uid // initialise system interface with os uid
seal.sys.I = system.New(seal.sys.user.uid) seal.sys.I = system.New(seal.sys.user.uid.unwrap())
seal.sys.I.IsVerbose = fmsg.Load seal.sys.I.IsVerbose = fmsg.Load
seal.sys.I.Verbose = fmsg.Verbose seal.sys.I.Verbose = fmsg.Verbose
seal.sys.I.Verbosef = fmsg.Verbosef seal.sys.I.Verbosef = fmsg.Verbosef
@ -267,7 +267,7 @@ func (a *app) Seal(config *fst.Config) error {
// verbose log seal information // verbose log seal information
fmsg.Verbosef("created application seal for uid %s (%s) groups: %v, command: %s", fmsg.Verbosef("created application seal for uid %s (%s) groups: %v, command: %s",
seal.sys.user.us, seal.sys.user.username, config.Confinement.Groups, config.Command) seal.sys.user.uid, seal.sys.user.username, config.Confinement.Groups, config.Command)
// seal app and release lock // seal app and release lock
a.seal = seal a.seal = seal

View File

@ -68,7 +68,7 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os sys.State) error {
seal.sys.UpdatePermType(system.User, targetTmpdirParent, acl.Execute) seal.sys.UpdatePermType(system.User, targetTmpdirParent, acl.Execute)
// ensure child tmpdir (e.g. `/tmp/fortify.%d/tmpdir/%d`) // ensure child tmpdir (e.g. `/tmp/fortify.%d/tmpdir/%d`)
targetTmpdir := path.Join(targetTmpdirParent, seal.sys.user.as) targetTmpdir := path.Join(targetTmpdirParent, seal.sys.user.aid.String())
seal.sys.Ensure(targetTmpdir, 01700) seal.sys.Ensure(targetTmpdir, 01700)
seal.sys.UpdatePermType(system.User, targetTmpdir, acl.Read, acl.Write, acl.Execute) seal.sys.UpdatePermType(system.User, targetTmpdir, acl.Read, acl.Write, acl.Execute)
seal.sys.bwrap.Bind(targetTmpdir, "/tmp", false, true) seal.sys.bwrap.Bind(targetTmpdir, "/tmp", false, true)
@ -126,9 +126,9 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os sys.State) error {
// generate /etc/passwd and /etc/group // generate /etc/passwd and /etc/group
seal.sys.bwrap.CopyBind("/etc/passwd", seal.sys.bwrap.CopyBind("/etc/passwd",
[]byte(username+":x:"+seal.sys.mappedIDString+":"+seal.sys.mappedIDString+":Fortify:"+homeDir+":"+sh+"\n")) []byte(username+":x:"+seal.sys.mapuid.String()+":"+seal.sys.mapuid.String()+":Fortify:"+homeDir+":"+sh+"\n"))
seal.sys.bwrap.CopyBind("/etc/group", seal.sys.bwrap.CopyBind("/etc/group",
[]byte("fortify:x:"+seal.sys.mappedIDString+":\n")) []byte("fortify:x:"+seal.sys.mapuid.String()+":\n"))
/* /*
Display servers Display servers
@ -181,7 +181,7 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os sys.State) error {
return fmsg.WrapError(ErrXDisplay, return fmsg.WrapError(ErrXDisplay,
"DISPLAY is not set") "DISPLAY is not set")
} else { } else {
seal.sys.ChangeHosts("#" + seal.sys.user.us) seal.sys.ChangeHosts("#" + seal.sys.user.uid.String())
seal.sys.bwrap.SetEnv[display] = d seal.sys.bwrap.SetEnv[display] = d
seal.sys.bwrap.Bind("/tmp/.X11-unix", "/tmp/.X11-unix") seal.sys.bwrap.Bind("/tmp/.X11-unix", "/tmp/.X11-unix")
} }

View File

@ -57,7 +57,7 @@ func (a *app) Run(ctx context.Context, rs *fst.RunState) error {
a.shim = new(shim.Shim) a.shim = new(shim.Shim)
waitErr := make(chan error, 1) waitErr := make(chan error, 1)
if startTime, err := a.shim.Start( if startTime, err := a.shim.Start(
a.seal.sys.user.as, a.seal.sys.user.aid.String(),
a.seal.sys.user.supp, a.seal.sys.user.supp,
a.seal.sys.sp, a.seal.sys.sp,
); err != nil { ); err != nil {
@ -90,14 +90,14 @@ func (a *app) Run(ctx context.Context, rs *fst.RunState) error {
// shim accepted setup payload, create process state // shim accepted setup payload, create process state
sd := state.State{ sd := state.State{
ID: *a.id, ID: a.id.unwrap(),
PID: a.shim.Unwrap().Process.Pid, PID: a.shim.Unwrap().Process.Pid,
Time: *startTime, Time: *startTime,
} }
// register process state // register process state
var err0 = new(StateStoreError) var err0 = new(StateStoreError)
err0.Inner, err0.DoErr = a.seal.store.Do(a.seal.sys.user.aid, func(c state.Cursor) { err0.Inner, err0.DoErr = a.seal.store.Do(a.seal.sys.user.aid.unwrap(), func(c state.Cursor) {
err0.InnerErr = c.Save(&sd, a.seal.ct) err0.InnerErr = c.Save(&sd, a.seal.ct)
}) })
a.seal.sys.saveState = true a.seal.sys.saveState = true
@ -147,11 +147,11 @@ func (a *app) Run(ctx context.Context, rs *fst.RunState) error {
// update store and revert app setup transaction // update store and revert app setup transaction
e := new(StateStoreError) e := new(StateStoreError)
e.Inner, e.DoErr = a.seal.store.Do(a.seal.sys.user.aid, func(b state.Cursor) { e.Inner, e.DoErr = a.seal.store.Do(a.seal.sys.user.aid.unwrap(), func(b state.Cursor) {
e.InnerErr = func() error { e.InnerErr = func() error {
// destroy defunct state entry // destroy defunct state entry
if cmd := a.shim.Unwrap(); cmd != nil && a.seal.sys.saveState { if cmd := a.shim.Unwrap(); cmd != nil && a.seal.sys.saveState {
if err := b.Destroy(*a.id); err != nil { if err := b.Destroy(a.id.unwrap()); err != nil {
return err return err
} }
} }

19
internal/app/strings.go Normal file
View File

@ -0,0 +1,19 @@
package app
import (
"strconv"
"git.gensokyo.uk/security/fortify/fst"
)
func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} }
func newID(id *fst.ID) *stringPair[fst.ID] { return &stringPair[fst.ID]{*id, id.String()} }
// stringPair stores a value and its string representation.
type stringPair[T comparable] struct {
v T
s string
}
func (s *stringPair[T]) unwrap() T { return s.v }
func (s *stringPair[T]) String() string { return s.s }

View File

@ -21,9 +21,7 @@ type appSealSys struct {
user appUser user appUser
// mapped uid and gid in user namespace // mapped uid and gid in user namespace
mappedID int mapuid *stringPair[int]
// string representation of mappedID
mappedIDString string
needRevert bool needRevert bool
saveState bool saveState bool
@ -33,19 +31,14 @@ type appSealSys struct {
} }
type appUser struct { type appUser struct {
// full uid resolved by fsu // application id
uid int aid *stringPair[int]
// string representation of uid // target uid resolved by fid:aid
us string uid *stringPair[int]
// supplementary group ids // supplementary group ids
supp []string supp []string
// application id
aid int
// string representation of aid
as string
// home directory host path // home directory host path
data string data string
// app user home directory // app user home directory