aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/app
diff options
context:
space:
mode:
authorOphestra Umiker <cat@ophivana.moe>2024-12-18 13:45:55 +0900
committerOphestra Umiker <cat@ophivana.moe>2024-12-18 13:45:55 +0900
commitb752ec44689aa5a3a0f3b9672b218691f161c7c1 (patch)
treeb514cb0c247f8deaa9feba84d9f8944623911f5d /internal/app
parent5d00805a7c775382516e05b5b01e58df651408ab (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/app')
-rw-r--r--internal/app/app.go28
-rw-r--r--internal/app/app_nixos_test.go9
-rw-r--r--internal/app/app_pd_test.go9
-rw-r--r--internal/app/app_test.go3
-rw-r--r--internal/app/config.go242
-rw-r--r--internal/app/seal.go16
-rw-r--r--internal/app/start.go17
7 files changed, 58 insertions, 266 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