diff options
| author | Ophestra Umiker <cat@ophivana.moe> | 2024-12-18 13:45:55 +0900 |
|---|---|---|
| committer | Ophestra Umiker <cat@ophivana.moe> | 2024-12-18 13:45:55 +0900 |
| commit | b752ec44689aa5a3a0f3b9672b218691f161c7c1 (patch) | |
| tree | b514cb0c247f8deaa9feba84d9f8944623911f5d /internal | |
| parent | 5d00805a7c775382516e05b5b01e58df651408ab (diff) | |
fipc: export config struct
Also store full config as part of state.
Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/app/app.go | 28 | ||||
| -rw-r--r-- | internal/app/app_nixos_test.go | 9 | ||||
| -rw-r--r-- | internal/app/app_pd_test.go | 9 | ||||
| -rw-r--r-- | internal/app/app_test.go | 3 | ||||
| -rw-r--r-- | internal/app/config.go | 242 | ||||
| -rw-r--r-- | internal/app/seal.go | 16 | ||||
| -rw-r--r-- | internal/app/start.go | 17 | ||||
| -rw-r--r-- | internal/state/print.go | 38 | ||||
| -rw-r--r-- | internal/state/simple.go | 4 | ||||
| -rw-r--r-- | internal/state/state.go | 14 |
10 files changed, 94 insertions, 286 deletions
diff --git a/internal/app/app.go b/internal/app/app.go index 3260deab..5cb1ddf8 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -2,8 +2,10 @@ package app import ( "sync" + "sync/atomic" "git.ophivana.moe/security/fortify/cmd/fshim/ipc/shim" + "git.ophivana.moe/security/fortify/fipc" "git.ophivana.moe/security/fortify/internal/linux" ) @@ -17,11 +19,14 @@ type App interface { // WaitErr returns error returned by the underlying wait syscall. WaitErr() error - Seal(config *Config) error + Seal(config *fipc.Config) error String() string } type app struct { + // single-use config reference + ct *appCt + // application unique identifier id *ID // operating system interface @@ -69,3 +74,24 @@ func New(os linux.System) (App, error) { a.os = os return a, newAppID(a.id) } + +// appCt ensures its wrapped val is only accessed once +type appCt struct { + val *fipc.Config + done *atomic.Bool +} + +func (a *appCt) Unwrap() *fipc.Config { + if !a.done.Load() { + defer a.done.Store(true) + return a.val + } + panic("attempted to access config reference twice") +} + +func newAppCt(config *fipc.Config) (ct *appCt) { + ct = new(appCt) + ct.done = new(atomic.Bool) + ct.val = config + return ct +} diff --git a/internal/app/app_nixos_test.go b/internal/app/app_nixos_test.go index 33380793..1d92ece8 100644 --- a/internal/app/app_nixos_test.go +++ b/internal/app/app_nixos_test.go @@ -3,6 +3,7 @@ package app_test import ( "git.ophivana.moe/security/fortify/acl" "git.ophivana.moe/security/fortify/dbus" + "git.ophivana.moe/security/fortify/fipc" "git.ophivana.moe/security/fortify/helper/bwrap" "git.ophivana.moe/security/fortify/internal/app" "git.ophivana.moe/security/fortify/internal/system" @@ -11,15 +12,15 @@ import ( var testCasesNixos = []sealTestCase{ { "nixos chromium direct wayland", new(stubNixOS), - &app.Config{ + &fipc.Config{ ID: "org.chromium.Chromium", Command: []string{"/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"}, - Confinement: app.ConfinementConfig{ + Confinement: fipc.ConfinementConfig{ AppID: 1, Groups: []string{}, Username: "u0_a1", Outer: "/var/lib/persist/module/fortify/0/1", - Sandbox: &app.SandboxConfig{ + Sandbox: &fipc.SandboxConfig{ UserNS: true, Net: true, MapRealUID: true, DirectWayland: true, Env: nil, - Filesystem: []*app.FilesystemConfig{ + Filesystem: []*fipc.FilesystemConfig{ {Src: "/bin", Must: true}, {Src: "/usr/bin", Must: true}, {Src: "/nix/store", Must: true}, {Src: "/run/current-system", Must: true}, {Src: "/sys/block"}, {Src: "/sys/bus"}, {Src: "/sys/class"}, {Src: "/sys/dev"}, {Src: "/sys/devices"}, diff --git a/internal/app/app_pd_test.go b/internal/app/app_pd_test.go index a97bbcf7..3a551805 100644 --- a/internal/app/app_pd_test.go +++ b/internal/app/app_pd_test.go @@ -3,6 +3,7 @@ package app_test import ( "git.ophivana.moe/security/fortify/acl" "git.ophivana.moe/security/fortify/dbus" + "git.ophivana.moe/security/fortify/fipc" "git.ophivana.moe/security/fortify/helper/bwrap" "git.ophivana.moe/security/fortify/internal/app" "git.ophivana.moe/security/fortify/internal/system" @@ -11,9 +12,9 @@ import ( var testCasesPd = []sealTestCase{ { "nixos permissive defaults no enablements", new(stubNixOS), - &app.Config{ + &fipc.Config{ Command: make([]string, 0), - Confinement: app.ConfinementConfig{ + Confinement: fipc.ConfinementConfig{ AppID: 0, Username: "chronos", Outer: "/home/chronos", @@ -190,10 +191,10 @@ var testCasesPd = []sealTestCase{ }, { "nixos permissive defaults chromium", new(stubNixOS), - &app.Config{ + &fipc.Config{ ID: "org.chromium.Chromium", Command: []string{"/run/current-system/sw/bin/zsh", "-c", "exec chromium "}, - Confinement: app.ConfinementConfig{ + Confinement: fipc.ConfinementConfig{ AppID: 9, Groups: []string{"video"}, Username: "chronos", diff --git a/internal/app/app_test.go b/internal/app/app_test.go index c61dcffc..21db1860 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "git.ophivana.moe/security/fortify/fipc" "git.ophivana.moe/security/fortify/helper/bwrap" "git.ophivana.moe/security/fortify/internal/app" "git.ophivana.moe/security/fortify/internal/linux" @@ -15,7 +16,7 @@ import ( type sealTestCase struct { name string os linux.System - config *app.Config + config *fipc.Config id app.ID wantSys *system.I wantBwrap *bwrap.Config diff --git a/internal/app/config.go b/internal/app/config.go deleted file mode 100644 index 758dcd08..00000000 --- a/internal/app/config.go +++ /dev/null @@ -1,242 +0,0 @@ -package app - -import ( - "errors" - - "git.ophivana.moe/security/fortify/dbus" - "git.ophivana.moe/security/fortify/helper/bwrap" - "git.ophivana.moe/security/fortify/internal/linux" - "git.ophivana.moe/security/fortify/internal/system" -) - -const fTmp = "/fortify" - -// Config is used to seal an *App -type Config struct { - // D-Bus application ID - ID string `json:"id"` - // value passed through to the child process as its argv - Command []string `json:"command"` - - // child confinement configuration - Confinement ConfinementConfig `json:"confinement"` -} - -// ConfinementConfig defines fortified child's confinement -type ConfinementConfig struct { - // numerical application id, determines uid in the init namespace - AppID int `json:"app_id"` - // list of supplementary groups to inherit - Groups []string `json:"groups"` - // passwd username in the sandbox, defaults to chronos - Username string `json:"username,omitempty"` - // home directory in sandbox, empty for outer - Inner string `json:"home_inner"` - // home directory in init namespace - Outer string `json:"home"` - // bwrap sandbox confinement configuration - Sandbox *SandboxConfig `json:"sandbox"` - - // reference to a system D-Bus proxy configuration, - // nil value disables system bus proxy - SystemBus *dbus.Config `json:"system_bus,omitempty"` - // reference to a session D-Bus proxy configuration, - // nil value makes session bus proxy assume built-in defaults - SessionBus *dbus.Config `json:"session_bus,omitempty"` - - // child capability enablements - Enablements system.Enablements `json:"enablements"` -} - -// SandboxConfig describes resources made available to the sandbox. -type SandboxConfig struct { - // unix hostname within sandbox - Hostname string `json:"hostname,omitempty"` - // userns availability within sandbox - UserNS bool `json:"userns,omitempty"` - // share net namespace - Net bool `json:"net,omitempty"` - // share all devices - Dev bool `json:"dev,omitempty"` - // do not run in new session - 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"` - // direct access to wayland socket - DirectWayland bool `json:"direct_wayland,omitempty"` - - // final environment variables - Env map[string]string `json:"env"` - // sandbox host filesystem access - Filesystem []*FilesystemConfig `json:"filesystem"` - // symlinks created inside the sandbox - Link [][2]string `json:"symlink"` - // automatically set up /etc symlinks - AutoEtc bool `json:"auto_etc"` - // paths to override by mounting tmpfs over them - Override []string `json:"override"` -} - -type FilesystemConfig struct { - // mount point in sandbox, same as src if empty - Dst string `json:"dst,omitempty"` - // host filesystem path to make available to sandbox - Src string `json:"src"` - // write access - Write bool `json:"write,omitempty"` - // device access - Device bool `json:"dev,omitempty"` - // exit if unable to share - Must bool `json:"require,omitempty"` -} - -// Bwrap returns the address of the corresponding bwrap.Config to s. -// Note that remaining tmpfs entries must be queued by the caller prior to launch. -func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) { - if s == nil { - return nil, errors.New("nil sandbox config") - } - - var uid int - if !s.MapRealUID { - uid = 65534 - } else { - uid = os.Geteuid() - } - - conf := (&bwrap.Config{ - Net: s.Net, - UserNS: s.UserNS, - Hostname: s.Hostname, - Clearenv: true, - SetEnv: s.Env, - NewSession: !s.NoNewSession, - DieWithParent: true, - AsInit: true, - - // initialise map - Chmod: make(bwrap.ChmodConfig), - }). - SetUID(uid).SetGID(uid). - Procfs("/proc"). - Tmpfs(fTmp, 4*1024) - - if !s.Dev { - conf.DevTmpfs("/dev").Mqueue("/dev/mqueue") - } else { - conf.Bind("/dev", "/dev", false, true, true) - } - - if !s.AutoEtc { - conf.Dir("/etc") - } - - for _, c := range s.Filesystem { - if c == nil { - continue - } - src := c.Src - dest := c.Dst - if c.Dst == "" { - dest = c.Src - } - conf.Bind(src, dest, !c.Must, c.Write, c.Device) - } - - for _, l := range s.Link { - conf.Symlink(l[0], l[1]) - } - - if s.AutoEtc { - conf.Bind("/etc", fTmp+"/etc") - - // link host /etc contents to prevent passwd/group from being overwritten - if d, err := os.ReadDir("/etc"); err != nil { - return nil, err - } else { - for _, ent := range d { - name := ent.Name() - switch name { - case "passwd": - case "group": - - case "mtab": - conf.Symlink("/proc/mounts", "/etc/"+name) - default: - conf.Symlink(fTmp+"/etc/"+name, "/etc/"+name) - } - } - } - } - - return conf, nil -} - -// Template returns a fully populated instance of Config. -func Template() *Config { - return &Config{ - ID: "org.chromium.Chromium", - Command: []string{ - "chromium", - "--ignore-gpu-blocklist", - "--disable-smooth-scrolling", - "--enable-features=UseOzonePlatform", - "--ozone-platform=wayland", - }, - Confinement: ConfinementConfig{ - AppID: 9, - Groups: []string{"video"}, - Username: "chronos", - 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, - DirectWayland: false, - // example API credentials pulled from Google Chrome - // DO NOT USE THESE IN A REAL BROWSER - Env: map[string]string{ - "GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY", - "GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com", - "GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT", - }, - Filesystem: []*FilesystemConfig{ - {Src: "/nix/store"}, - {Src: "/run/current-system"}, - {Src: "/run/opengl-driver"}, - {Src: "/var/db/nix-channels"}, - {Src: "/home/chronos", Write: true, Must: true}, - {Src: "/dev/dri", Device: true}, - }, - Link: [][2]string{{"/run/user/65534", "/run/user/150"}}, - AutoEtc: true, - Override: []string{"/var/run/nscd"}, - }, - SystemBus: &dbus.Config{ - See: nil, - Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"}, - Own: nil, - Call: nil, - Broadcast: nil, - Log: false, - Filter: true, - }, - SessionBus: &dbus.Config{ - See: nil, - Talk: []string{"org.freedesktop.Notifications", "org.freedesktop.FileManager1", "org.freedesktop.ScreenSaver", - "org.freedesktop.secrets", "org.kde.kwalletd5", "org.kde.kwalletd6", "org.gnome.SessionManager"}, - Own: []string{"org.chromium.Chromium.*", "org.mpris.MediaPlayer2.org.chromium.Chromium.*", - "org.mpris.MediaPlayer2.chromium.*"}, - Call: map[string]string{"org.freedesktop.portal.*": "*"}, - Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"}, - Log: false, - Filter: true, - }, - Enablements: system.EWayland.Mask() | system.EDBus.Mask() | system.EPulse.Mask(), - }, - } -} diff --git a/internal/app/seal.go b/internal/app/seal.go index 94c9e061..b01befc1 100644 --- a/internal/app/seal.go +++ b/internal/app/seal.go @@ -9,6 +9,7 @@ import ( "strconv" "git.ophivana.moe/security/fortify/dbus" + "git.ophivana.moe/security/fortify/fipc" "git.ophivana.moe/security/fortify/internal/fmsg" "git.ophivana.moe/security/fortify/internal/linux" "git.ophivana.moe/security/fortify/internal/state" @@ -59,7 +60,7 @@ type appSeal struct { } // Seal seals the app launch context -func (a *app) Seal(config *Config) error { +func (a *app) Seal(config *fipc.Config) error { a.lock.Lock() defer a.lock.Unlock() @@ -147,7 +148,7 @@ func (a *app) Seal(config *Config) error { fmsg.VPrintln("sandbox configuration not supplied, PROCEED WITH CAUTION") // permissive defaults - conf := &SandboxConfig{ + conf := &fipc.SandboxConfig{ UserNS: true, Net: true, NoNewSession: true, @@ -157,7 +158,7 @@ func (a *app) Seal(config *Config) error { if d, err := a.os.ReadDir("/"); err != nil { return err } else { - b := make([]*FilesystemConfig, 0, len(d)) + b := make([]*fipc.FilesystemConfig, 0, len(d)) for _, ent := range d { p := "/" + ent.Name() switch p { @@ -169,7 +170,7 @@ func (a *app) Seal(config *Config) error { case "/etc": default: - b = append(b, &FilesystemConfig{Src: p, Write: true, Must: true}) + b = append(b, &fipc.FilesystemConfig{Src: p, Write: true, Must: true}) } } conf.Filesystem = append(conf.Filesystem, b...) @@ -178,7 +179,7 @@ func (a *app) Seal(config *Config) error { if d, err := a.os.ReadDir("/run"); err != nil { return err } else { - b := make([]*FilesystemConfig, 0, len(d)) + b := make([]*fipc.FilesystemConfig, 0, len(d)) for _, ent := range d { name := ent.Name() switch name { @@ -186,7 +187,7 @@ func (a *app) Seal(config *Config) error { case "dbus": default: p := "/run/" + name - b = append(b, &FilesystemConfig{Src: p, Write: true, Must: true}) + b = append(b, &fipc.FilesystemConfig{Src: p, Write: true, Must: true}) } } conf.Filesystem = append(conf.Filesystem, b...) @@ -198,7 +199,7 @@ func (a *app) Seal(config *Config) error { } // bind GPU stuff if config.Confinement.Enablements.Has(system.EX11) || config.Confinement.Enablements.Has(system.EWayland) { - conf.Filesystem = append(conf.Filesystem, &FilesystemConfig{Src: "/dev/dri", Device: true}) + conf.Filesystem = append(conf.Filesystem, &fipc.FilesystemConfig{Src: "/dev/dri", Device: true}) } config.Confinement.Sandbox = conf @@ -236,5 +237,6 @@ func (a *app) Seal(config *Config) error { // seal app and release lock a.seal = seal + a.ct = newAppCt(config) return nil } diff --git a/internal/app/start.go b/internal/app/start.go index e1247d4c..91864510 100644 --- a/internal/app/start.go +++ b/internal/app/start.go @@ -70,11 +70,10 @@ func (a *app) Start() error { } else { // shim start and setup success, create process state sd := state.State{ - PID: a.shim.Unwrap().Process.Pid, - Command: a.seal.command, - Capability: a.seal.et, - Argv: a.shim.Unwrap().Args, - Time: *startTime, + ID: *a.id, + PID: a.shim.Unwrap().Process.Pid, + Config: a.ct.Unwrap(), + Time: *startTime, } // register process state @@ -227,8 +226,12 @@ func (a *app) Wait() (int, error) { } // accumulate capabilities of other launchers - for _, s := range states { - *rt |= s.Capability + for i, s := range states { + if s.Config != nil { + *rt |= s.Config.Confinement.Enablements + } else { + fmsg.Printf("state entry %d does not contain config", i) + } } } // invert accumulated enablements for cleanup diff --git a/internal/state/print.go b/internal/state/print.go index 174f0693..c747c04e 100644 --- a/internal/state/print.go +++ b/internal/state/print.go @@ -82,27 +82,41 @@ func (s *simpleStore) mustPrintLauncherState(w **tabwriter.Writer, now time.Time continue } - // build enablements string - ets := strings.Builder{} - // append enablement strings in order - for i := system.Enablement(0); i < system.Enablement(system.ELen); i++ { - if state.Capability.Has(i) { - ets.WriteString(", " + i.String()) + // build enablements and command string + var ( + ets *strings.Builder + cs = "(No command information)" + ) + + // check if enablements are provided + if state.Config != nil { + ets = new(strings.Builder) + // append enablement strings in order + for i := system.Enablement(0); i < system.Enablement(system.ELen); i++ { + if state.Config.Confinement.Enablements.Has(i) { + ets.WriteString(", " + i.String()) + } } + + cs = fmt.Sprintf("%q", state.Config.Command) } - // prevent an empty string when - if ets.Len() == 0 { - ets.WriteString("(No enablements)") + if ets != nil { + // prevent an empty string + if ets.Len() == 0 { + ets.WriteString("(No enablements)") + } + } else { + ets = new(strings.Builder) + ets.WriteString("(No confinement information)") } if !fmsg.Verbose() { _, _ = fmt.Fprintf(*w, "\t%d\t%s\t%s\t%s\t%s\n", - state.PID, s.path[len(s.path)-1], now.Sub(state.Time).Round(time.Second).String(), strings.TrimPrefix(ets.String(), ", "), - state.Command) + state.PID, s.path[len(s.path)-1], now.Sub(state.Time).Round(time.Second).String(), strings.TrimPrefix(ets.String(), ", "), cs) } else { // emit argv instead when verbose _, _ = fmt.Fprintf(*w, "\t%d\t%s\t%s\n", - state.PID, s.path[len(s.path)-1], state.Argv) + state.PID, s.path[len(s.path)-1], state.ID) } } diff --git a/internal/state/simple.go b/internal/state/simple.go index ad3e4726..1a26f6f7 100644 --- a/internal/state/simple.go +++ b/internal/state/simple.go @@ -176,6 +176,10 @@ func (b *simpleBackend) Save(state *State) error { b.lock.Lock() defer b.lock.Unlock() + if state.Config == nil { + return errors.New("state does not contain config") + } + statePath := b.filename(state.PID) // create and open state data file diff --git a/internal/state/state.go b/internal/state/state.go index ee5a95f7..bb23932a 100644 --- a/internal/state/state.go +++ b/internal/state/state.go @@ -3,7 +3,7 @@ package state import ( "time" - "git.ophivana.moe/security/fortify/internal/system" + "git.ophivana.moe/security/fortify/fipc" ) type Store interface { @@ -26,15 +26,13 @@ type Backend interface { // State is the on-disk format for a fortified process's state information type State struct { + // fortify instance id + ID [16]byte `json:"instance"` // child process PID value - PID int - // command used to seal the app - Command []string - // capability enablements applied to child - Capability system.Enablements + PID int `json:"pid"` + // sealed app configuration + Config *fipc.Config `json:"config"` - // full argv whe launching - Argv []string // process start time Time time.Time } |
