diff --git a/internal/app/config.go b/fipc/config.go similarity index 99% rename from internal/app/config.go rename to fipc/config.go index 758dcd0..472d22f 100644 --- a/internal/app/config.go +++ b/fipc/config.go @@ -1,4 +1,4 @@ -package app +package fipc import ( "errors" diff --git a/internal/app/app.go b/internal/app/app.go index 3260dea..5cb1ddf 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 3338079..1d92ece 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 a97bbcf..3a55180 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 c61dcff..21db186 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/seal.go b/internal/app/seal.go index 94c9e06..b01befc 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 e1247d4..9186451 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 174f069..c747c04 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 ad3e472..1a26f6f 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 ee5a95f..bb23932 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 } diff --git a/main.go b/main.go index 4074bfa..3087cd5 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,7 @@ import ( "text/tabwriter" "git.ophivana.moe/security/fortify/dbus" + "git.ophivana.moe/security/fortify/fipc" "git.ophivana.moe/security/fortify/internal" "git.ophivana.moe/security/fortify/internal/app" "git.ophivana.moe/security/fortify/internal/fmsg" @@ -102,7 +103,7 @@ func main() { fmt.Println(license) fmsg.Exit(0) case "template": // print full template configuration - if s, err := json.MarshalIndent(app.Template(), "", " "); err != nil { + if s, err := json.MarshalIndent(fipc.Template(), "", " "); err != nil { fmsg.Fatalf("cannot generate template: %v", err) panic("unreachable") } else { @@ -129,7 +130,7 @@ func main() { fmsg.Fatal("app requires at least 1 argument") } - config := new(app.Config) + config := new(fipc.Config) if f, err := os.Open(args[1]); err != nil { fmsg.Fatalf("cannot access config file %q: %s", args[1], err) panic("unreachable") @@ -179,7 +180,7 @@ func main() { _ = set.Parse(args[1:]) // initialise config from flags - config := &app.Config{ + config := &fipc.Config{ ID: fid, Command: set.Args(), } @@ -275,7 +276,7 @@ func main() { panic("unreachable") } -func runApp(config *app.Config) { +func runApp(config *fipc.Config) { if os.SdBooted() { fmsg.VPrintln("system booted with systemd as init system") }