aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-04-12 13:56:41 +0900
committerOphestra <cat@gensokyo.uk>2025-04-12 13:56:41 +0900
commit6309469e933a31a300fbf16d8e77f48dcee402d3 (patch)
tree8f8a72ee02b3ca15b104a6e55c9379e20f8d7e8a /internal
parent0d7c1a9a4356614f035225aeb24e66421879a99b (diff)
app/instance: wrap internal implementation
This reduces the scope of the fst package, which was growing questionably large. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal')
-rw-r--r--internal/app/app.go59
-rw-r--r--internal/app/id.go48
-rw-r--r--internal/app/id_test.go63
-rw-r--r--internal/app/instance/common/container.go187
-rw-r--r--internal/app/instance/common/path.go11
-rw-r--r--internal/app/instance/common/path_test.go85
-rw-r--r--internal/app/instance/errors.go17
-rw-r--r--internal/app/instance/new.go33
-rw-r--r--internal/app/instance/shim.go6
-rw-r--r--internal/app/internal/setuid/app.go (renamed from internal/app/setuid/app.go)22
-rw-r--r--internal/app/internal/setuid/app_nixos_test.go (renamed from internal/app/setuid/app_nixos_test.go)3
-rw-r--r--internal/app/internal/setuid/app_pd_test.go (renamed from internal/app/setuid/app_pd_test.go)5
-rw-r--r--internal/app/internal/setuid/app_stub_test.go (renamed from internal/app/setuid/app_stub_test.go)6
-rw-r--r--internal/app/internal/setuid/app_test.go (renamed from internal/app/setuid/app_test.go)5
-rw-r--r--internal/app/internal/setuid/errors.go (renamed from internal/app/setuid/errors.go)4
-rw-r--r--internal/app/internal/setuid/export_test.go (renamed from internal/app/setuid/export_test.go)6
-rw-r--r--internal/app/internal/setuid/process.go (renamed from internal/app/setuid/process.go)4
-rw-r--r--internal/app/internal/setuid/seal.go (renamed from internal/app/setuid/seal.go)8
-rw-r--r--internal/app/internal/setuid/shim.go (renamed from internal/app/setuid/shim.go)0
-rw-r--r--internal/app/internal/setuid/strings.go (renamed from internal/app/setuid/strings.go)6
-rw-r--r--internal/state/multi.go9
-rw-r--r--internal/state/state.go7
-rw-r--r--internal/state/state_test.go3
-rw-r--r--internal/sys/interface.go6
-rw-r--r--internal/sys/std.go6
25 files changed, 559 insertions, 50 deletions
diff --git a/internal/app/app.go b/internal/app/app.go
new file mode 100644
index 00000000..325d9f87
--- /dev/null
+++ b/internal/app/app.go
@@ -0,0 +1,59 @@
+// Package app defines the generic [App] interface.
+package app
+
+import (
+ "syscall"
+ "time"
+
+ "git.gensokyo.uk/security/fortify/fst"
+)
+
+type App interface {
+ // ID returns a copy of [ID] held by App.
+ ID() ID
+
+ // Seal determines the outcome of config as a [SealedApp].
+ // The value of config might be overwritten and must not be used again.
+ Seal(config *fst.Config) (SealedApp, error)
+
+ String() string
+}
+
+type SealedApp interface {
+ // Run commits sealed system setup and starts the app process.
+ Run(rs *RunState) error
+}
+
+// RunState stores the outcome of a call to [SealedApp.Run].
+type RunState struct {
+ // Time is the exact point in time where the process was created.
+ // Location must be set to UTC.
+ //
+ // Time is nil if no process was ever created.
+ Time *time.Time
+ // RevertErr is stored by the deferred revert call.
+ RevertErr error
+ // WaitErr is the generic error value created by the standard library.
+ WaitErr error
+
+ syscall.WaitStatus
+}
+
+// SetStart stores the current time in [RunState] once.
+func (rs *RunState) SetStart() {
+ if rs.Time != nil {
+ panic("attempted to store time twice")
+ }
+ now := time.Now().UTC()
+ rs.Time = &now
+}
+
+// Paths contains environment-dependent paths used by fortify.
+type Paths struct {
+ // path to shared directory (usually `/tmp/fortify.%d`)
+ SharePath string `json:"share_path"`
+ // XDG_RUNTIME_DIR value (usually `/run/user/%d`)
+ RuntimePath string `json:"runtime_path"`
+ // application runtime directory (usually `/run/user/%d/fortify`)
+ RunDirPath string `json:"run_dir_path"`
+}
diff --git a/internal/app/id.go b/internal/app/id.go
new file mode 100644
index 00000000..e674c7dd
--- /dev/null
+++ b/internal/app/id.go
@@ -0,0 +1,48 @@
+package app
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "errors"
+ "fmt"
+)
+
+type ID [16]byte
+
+var (
+ ErrInvalidLength = errors.New("string representation must have a length of 32")
+)
+
+func (a *ID) String() string {
+ return hex.EncodeToString(a[:])
+}
+
+func NewAppID(id *ID) error {
+ _, err := rand.Read(id[:])
+ return err
+}
+
+func ParseAppID(id *ID, s string) error {
+ if len(s) != 32 {
+ return ErrInvalidLength
+ }
+
+ for i, b := range s {
+ if b < '0' || b > 'f' {
+ return fmt.Errorf("invalid char %q at byte %d", b, i)
+ }
+
+ v := uint8(b)
+ if v > '9' {
+ v = 10 + v - 'a'
+ } else {
+ v -= '0'
+ }
+ if i%2 == 0 {
+ v <<= 4
+ }
+ id[i/2] += v
+ }
+
+ return nil
+}
diff --git a/internal/app/id_test.go b/internal/app/id_test.go
new file mode 100644
index 00000000..f928a482
--- /dev/null
+++ b/internal/app/id_test.go
@@ -0,0 +1,63 @@
+package app_test
+
+import (
+ "errors"
+ "testing"
+
+ . "git.gensokyo.uk/security/fortify/internal/app"
+)
+
+func TestParseAppID(t *testing.T) {
+ t.Run("bad length", func(t *testing.T) {
+ if err := ParseAppID(new(ID), "meow"); !errors.Is(err, ErrInvalidLength) {
+ t.Errorf("ParseAppID: error = %v, wantErr = %v", err, ErrInvalidLength)
+ }
+ })
+
+ t.Run("bad byte", func(t *testing.T) {
+ wantErr := "invalid char '\\n' at byte 15"
+ if err := ParseAppID(new(ID), "02bc7f8936b2af6\n\ne2535cd71ef0bb7"); err == nil || err.Error() != wantErr {
+ t.Errorf("ParseAppID: error = %v, wantErr = %v", err, wantErr)
+ }
+ })
+
+ t.Run("fuzz 16 iterations", func(t *testing.T) {
+ for i := 0; i < 16; i++ {
+ testParseAppIDWithRandom(t)
+ }
+ })
+}
+
+func FuzzParseAppID(f *testing.F) {
+ for i := 0; i < 16; i++ {
+ id := new(ID)
+ if err := NewAppID(id); err != nil {
+ panic(err.Error())
+ }
+ f.Add(id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15])
+ }
+
+ f.Fuzz(func(t *testing.T, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 byte) {
+ testParseAppID(t, &ID{b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15})
+ })
+}
+
+func testParseAppIDWithRandom(t *testing.T) {
+ id := new(ID)
+ if err := NewAppID(id); err != nil {
+ t.Fatalf("cannot generate app ID: %v", err)
+ }
+ testParseAppID(t, id)
+}
+
+func testParseAppID(t *testing.T, id *ID) {
+ s := id.String()
+ got := new(ID)
+ if err := ParseAppID(got, s); err != nil {
+ t.Fatalf("cannot parse app ID: %v", err)
+ }
+
+ if *got != *id {
+ t.Fatalf("ParseAppID(%#v) = \n%#v, want \n%#v", s, got, id)
+ }
+}
diff --git a/internal/app/instance/common/container.go b/internal/app/instance/common/container.go
new file mode 100644
index 00000000..cc26c803
--- /dev/null
+++ b/internal/app/instance/common/container.go
@@ -0,0 +1,187 @@
+package common
+
+import (
+ "errors"
+ "fmt"
+ "io/fs"
+ "maps"
+ "path"
+ "slices"
+ "syscall"
+
+ "git.gensokyo.uk/security/fortify/dbus"
+ "git.gensokyo.uk/security/fortify/fst"
+ "git.gensokyo.uk/security/fortify/internal/sys"
+ "git.gensokyo.uk/security/fortify/sandbox"
+ "git.gensokyo.uk/security/fortify/sandbox/seccomp"
+)
+
+// NewContainer initialises [sandbox.Params] via [fst.SandboxConfig].
+// Note that remaining container setup must be queued by the caller.
+func NewContainer(s *fst.SandboxConfig, os sys.State, uid, gid *int) (*sandbox.Params, map[string]string, error) {
+ if s == nil {
+ return nil, nil, syscall.EBADE
+ }
+
+ container := &sandbox.Params{
+ Hostname: s.Hostname,
+ Ops: new(sandbox.Ops),
+ Seccomp: s.Seccomp,
+ }
+
+ if s.Multiarch {
+ container.Seccomp |= seccomp.FilterMultiarch
+ }
+
+ /* this is only 4 KiB of memory on a 64-bit system,
+ permissive defaults on NixOS results in around 100 entries
+ so this capacity should eliminate copies for most setups */
+ *container.Ops = slices.Grow(*container.Ops, 1<<8)
+
+ if s.Devel {
+ container.Flags |= sandbox.FAllowDevel
+ }
+ if s.Userns {
+ container.Flags |= sandbox.FAllowUserns
+ }
+ if s.Net {
+ container.Flags |= sandbox.FAllowNet
+ }
+ if s.Tty {
+ container.Flags |= sandbox.FAllowTTY
+ }
+
+ if s.MapRealUID {
+ /* some programs fail to connect to dbus session running as a different uid
+ so this workaround is introduced to map priv-side caller uid in container */
+ container.Uid = os.Getuid()
+ *uid = container.Uid
+ container.Gid = os.Getgid()
+ *gid = container.Gid
+ } else {
+ *uid = sandbox.OverflowUid()
+ *gid = sandbox.OverflowGid()
+ }
+
+ container.
+ Proc("/proc").
+ Tmpfs(fst.Tmp, 1<<12, 0755)
+
+ if !s.Device {
+ container.Dev("/dev").Mqueue("/dev/mqueue")
+ } else {
+ container.Bind("/dev", "/dev", sandbox.BindWritable|sandbox.BindDevice)
+ }
+
+ /* retrieve paths and hide them if they're made available in the sandbox;
+ this feature tries to improve user experience of permissive defaults, and
+ to warn about issues in custom configuration; it is NOT a security feature
+ and should not be treated as such, ALWAYS be careful with what you bind */
+ var hidePaths []string
+ sc := os.Paths()
+ hidePaths = append(hidePaths, sc.RuntimePath, sc.SharePath)
+ _, systemBusAddr := dbus.Address()
+ if entries, err := dbus.Parse([]byte(systemBusAddr)); err != nil {
+ return nil, nil, err
+ } else {
+ // there is usually only one, do not preallocate
+ for _, entry := range entries {
+ if entry.Method != "unix" {
+ continue
+ }
+ for _, pair := range entry.Values {
+ if pair[0] == "path" {
+ if path.IsAbs(pair[1]) {
+ // get parent dir of socket
+ dir := path.Dir(pair[1])
+ if dir == "." || dir == "/" {
+ os.Printf("dbus socket %q is in an unusual location", pair[1])
+ }
+ hidePaths = append(hidePaths, dir)
+ } else {
+ os.Printf("dbus socket %q is not absolute", pair[1])
+ }
+ }
+ }
+ }
+ }
+ hidePathMatch := make([]bool, len(hidePaths))
+ for i := range hidePaths {
+ if err := evalSymlinks(os, &hidePaths[i]); err != nil {
+ return nil, nil, err
+ }
+ }
+
+ for _, c := range s.Filesystem {
+ if c == nil {
+ continue
+ }
+
+ if !path.IsAbs(c.Src) {
+ return nil, nil, fmt.Errorf("src path %q is not absolute", c.Src)
+ }
+
+ dest := c.Dst
+ if c.Dst == "" {
+ dest = c.Src
+ } else if !path.IsAbs(dest) {
+ return nil, nil, fmt.Errorf("dst path %q is not absolute", dest)
+ }
+
+ srcH := c.Src
+ if err := evalSymlinks(os, &srcH); err != nil {
+ return nil, nil, err
+ }
+
+ for i := range hidePaths {
+ // skip matched entries
+ if hidePathMatch[i] {
+ continue
+ }
+
+ if ok, err := deepContainsH(srcH, hidePaths[i]); err != nil {
+ return nil, nil, err
+ } else if ok {
+ hidePathMatch[i] = true
+ os.Printf("hiding paths from %q", c.Src)
+ }
+ }
+
+ var flags int
+ if c.Write {
+ flags |= sandbox.BindWritable
+ }
+ if c.Device {
+ flags |= sandbox.BindDevice | sandbox.BindWritable
+ }
+ if !c.Must {
+ flags |= sandbox.BindOptional
+ }
+ container.Bind(c.Src, dest, flags)
+ }
+
+ // cover matched paths
+ for i, ok := range hidePathMatch {
+ if ok {
+ container.Tmpfs(hidePaths[i], 1<<13, 0755)
+ }
+ }
+
+ for _, l := range s.Link {
+ container.Link(l[0], l[1])
+ }
+
+ return container, maps.Clone(s.Env), nil
+}
+
+func evalSymlinks(os sys.State, v *string) error {
+ if p, err := os.EvalSymlinks(*v); err != nil {
+ if !errors.Is(err, fs.ErrNotExist) {
+ return err
+ }
+ os.Printf("path %q does not yet exist", *v)
+ } else {
+ *v = p
+ }
+ return nil
+}
diff --git a/internal/app/instance/common/path.go b/internal/app/instance/common/path.go
new file mode 100644
index 00000000..ce40f4f1
--- /dev/null
+++ b/internal/app/instance/common/path.go
@@ -0,0 +1,11 @@
+package common
+
+import (
+ "path/filepath"
+ "strings"
+)
+
+func deepContainsH(basepath, targpath string) (bool, error) {
+ rel, err := filepath.Rel(basepath, targpath)
+ return err == nil && rel != ".." && !strings.HasPrefix(rel, string([]byte{'.', '.', filepath.Separator})), err
+}
diff --git a/internal/app/instance/common/path_test.go b/internal/app/instance/common/path_test.go
new file mode 100644
index 00000000..b14f24df
--- /dev/null
+++ b/internal/app/instance/common/path_test.go
@@ -0,0 +1,85 @@
+package common
+
+import (
+ "testing"
+)
+
+func TestDeepContainsH(t *testing.T) {
+ testCases := []struct {
+ name string
+ basepath string
+ targpath string
+ want bool
+ wantErr bool
+ }{
+ {
+ name: "empty",
+ want: true,
+ },
+ {
+ name: "equal abs",
+ basepath: "/run",
+ targpath: "/run",
+ want: true,
+ },
+ {
+ name: "equal rel",
+ basepath: "./run",
+ targpath: "run",
+ want: true,
+ },
+ {
+ name: "contains abs",
+ basepath: "/run",
+ targpath: "/run/dbus",
+ want: true,
+ },
+ {
+ name: "inverse contains abs",
+ basepath: "/run/dbus",
+ targpath: "/run",
+ want: false,
+ },
+ {
+ name: "contains rel",
+ basepath: "../run",
+ targpath: "../run/dbus",
+ want: true,
+ },
+ {
+ name: "inverse contains rel",
+ basepath: "../run/dbus",
+ targpath: "../run",
+ want: false,
+ },
+ {
+ name: "weird abs",
+ basepath: "/run/dbus",
+ targpath: "/run/dbus/../current-system",
+ want: false,
+ },
+ {
+ name: "weird rel",
+ basepath: "../run/dbus",
+ targpath: "../run/dbus/../current-system",
+ want: false,
+ },
+
+ {
+ name: "invalid mix",
+ basepath: "/run",
+ targpath: "./run",
+ wantErr: true,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ if got, err := deepContainsH(tc.basepath, tc.targpath); (err != nil) != tc.wantErr {
+ t.Errorf("deepContainsH() error = %v, wantErr %v", err, tc.wantErr)
+ } else if got != tc.want {
+ t.Errorf("deepContainsH() = %v, want %v", got, tc.want)
+ }
+ })
+ }
+}
diff --git a/internal/app/instance/errors.go b/internal/app/instance/errors.go
new file mode 100644
index 00000000..b3331e3d
--- /dev/null
+++ b/internal/app/instance/errors.go
@@ -0,0 +1,17 @@
+package instance
+
+import (
+ "syscall"
+
+ "git.gensokyo.uk/security/fortify/internal/app"
+ "git.gensokyo.uk/security/fortify/internal/app/internal/setuid"
+)
+
+func PrintRunStateErr(whence int, rs *app.RunState, runErr error) (code int) {
+ switch whence {
+ case ISetuid:
+ return setuid.PrintRunStateErr(rs, runErr)
+ default:
+ panic(syscall.EINVAL)
+ }
+}
diff --git a/internal/app/instance/new.go b/internal/app/instance/new.go
new file mode 100644
index 00000000..cb5e8b27
--- /dev/null
+++ b/internal/app/instance/new.go
@@ -0,0 +1,33 @@
+// Package instance exposes cross-package implementation details and provides constructors for builtin implementations.
+package instance
+
+import (
+ "context"
+ "log"
+ "syscall"
+
+ "git.gensokyo.uk/security/fortify/internal/app"
+ "git.gensokyo.uk/security/fortify/internal/app/internal/setuid"
+ "git.gensokyo.uk/security/fortify/internal/sys"
+)
+
+const (
+ ISetuid = iota
+)
+
+func New(whence int, ctx context.Context, os sys.State) (app.App, error) {
+ switch whence {
+ case ISetuid:
+ return setuid.New(ctx, os)
+ default:
+ return nil, syscall.EINVAL
+ }
+}
+
+func MustNew(whence int, ctx context.Context, os sys.State) app.App {
+ a, err := New(whence, ctx, os)
+ if err != nil {
+ log.Fatalf("cannot create app: %v", err)
+ }
+ return a
+}
diff --git a/internal/app/instance/shim.go b/internal/app/instance/shim.go
new file mode 100644
index 00000000..bc497ad3
--- /dev/null
+++ b/internal/app/instance/shim.go
@@ -0,0 +1,6 @@
+package instance
+
+import "git.gensokyo.uk/security/fortify/internal/app/internal/setuid"
+
+// ShimMain is the main function of the shim process and runs as the unconstrained target user.
+func ShimMain() { setuid.ShimMain() }
diff --git a/internal/app/setuid/app.go b/internal/app/internal/setuid/app.go
index 472d06ea..6af224c0 100644
--- a/internal/app/setuid/app.go
+++ b/internal/app/internal/setuid/app.go
@@ -3,36 +3,28 @@ package setuid
import (
"context"
"fmt"
- "log"
"sync"
"git.gensokyo.uk/security/fortify/fst"
+ . "git.gensokyo.uk/security/fortify/internal/app"
"git.gensokyo.uk/security/fortify/internal/fmsg"
"git.gensokyo.uk/security/fortify/internal/sys"
)
-func New(ctx context.Context, os sys.State) (fst.App, error) {
+func New(ctx context.Context, os sys.State) (App, error) {
a := new(app)
a.sys = os
a.ctx = ctx
- id := new(fst.ID)
- err := fst.NewAppID(id)
+ id := new(ID)
+ err := NewAppID(id)
a.id = newID(id)
return a, err
}
-func MustNew(ctx context.Context, os sys.State) fst.App {
- a, err := New(ctx, os)
- if err != nil {
- log.Fatalf("cannot create app: %v", err)
- }
- return a
-}
-
type app struct {
- id *stringPair[fst.ID]
+ id *stringPair[ID]
sys sys.State
ctx context.Context
@@ -40,7 +32,7 @@ type app struct {
mu sync.RWMutex
}
-func (a *app) ID() fst.ID { a.mu.RLock(); defer a.mu.RUnlock(); return a.id.unwrap() }
+func (a *app) ID() ID { a.mu.RLock(); defer a.mu.RUnlock(); return a.id.unwrap() }
func (a *app) String() string {
if a == nil {
@@ -60,7 +52,7 @@ func (a *app) String() string {
return fmt.Sprintf("(unsealed app %s)", a.id)
}
-func (a *app) Seal(config *fst.Config) (fst.SealedApp, error) {
+func (a *app) Seal(config *fst.Config) (SealedApp, error) {
a.mu.Lock()
defer a.mu.Unlock()
diff --git a/internal/app/setuid/app_nixos_test.go b/internal/app/internal/setuid/app_nixos_test.go
index 6469f235..0daaf690 100644
--- a/internal/app/setuid/app_nixos_test.go
+++ b/internal/app/internal/setuid/app_nixos_test.go
@@ -4,6 +4,7 @@ import (
"git.gensokyo.uk/security/fortify/acl"
"git.gensokyo.uk/security/fortify/dbus"
"git.gensokyo.uk/security/fortify/fst"
+ "git.gensokyo.uk/security/fortify/internal/app"
"git.gensokyo.uk/security/fortify/sandbox"
"git.gensokyo.uk/security/fortify/system"
)
@@ -48,7 +49,7 @@ var testCasesNixos = []sealTestCase{
Enablements: system.EWayland | system.EDBus | system.EPulse,
},
},
- fst.ID{
+ app.ID{
0x8e, 0x2c, 0x76, 0xb0,
0x66, 0xda, 0xbe, 0x57,
0x4c, 0xf0, 0x73, 0xbd,
diff --git a/internal/app/setuid/app_pd_test.go b/internal/app/internal/setuid/app_pd_test.go
index c4ab5797..2dc06253 100644
--- a/internal/app/setuid/app_pd_test.go
+++ b/internal/app/internal/setuid/app_pd_test.go
@@ -6,6 +6,7 @@ import (
"git.gensokyo.uk/security/fortify/acl"
"git.gensokyo.uk/security/fortify/dbus"
"git.gensokyo.uk/security/fortify/fst"
+ "git.gensokyo.uk/security/fortify/internal/app"
"git.gensokyo.uk/security/fortify/sandbox"
"git.gensokyo.uk/security/fortify/system"
)
@@ -20,7 +21,7 @@ var testCasesPd = []sealTestCase{
Outer: "/home/chronos",
},
},
- fst.ID{
+ app.ID{
0x4a, 0x45, 0x0b, 0x65,
0x96, 0xd7, 0xbc, 0x15,
0xbd, 0x01, 0x78, 0x0e,
@@ -117,7 +118,7 @@ var testCasesPd = []sealTestCase{
Enablements: system.EWayland | system.EDBus | system.EPulse,
},
},
- fst.ID{
+ app.ID{
0xeb, 0xf0, 0x83, 0xd1,
0xb1, 0x75, 0x91, 0x17,
0x82, 0xd4, 0x13, 0x36,
diff --git a/internal/app/setuid/app_stub_test.go b/internal/app/internal/setuid/app_stub_test.go
index 0b414f25..c3d0a67f 100644
--- a/internal/app/setuid/app_stub_test.go
+++ b/internal/app/internal/setuid/app_stub_test.go
@@ -7,7 +7,7 @@ import (
"os/user"
"strconv"
- "git.gensokyo.uk/security/fortify/fst"
+ "git.gensokyo.uk/security/fortify/internal/app"
)
// fs methods are not implemented using a real FS
@@ -125,8 +125,8 @@ func (s *stubNixOS) Open(name string) (fs.File, error) {
}
}
-func (s *stubNixOS) Paths() fst.Paths {
- return fst.Paths{
+func (s *stubNixOS) Paths() app.Paths {
+ return app.Paths{
SharePath: "/tmp/fortify.1971",
RuntimePath: "/run/user/1971",
RunDirPath: "/run/user/1971/fortify",
diff --git a/internal/app/setuid/app_test.go b/internal/app/internal/setuid/app_test.go
index 4454e6ff..9e08c077 100644
--- a/internal/app/setuid/app_test.go
+++ b/internal/app/internal/setuid/app_test.go
@@ -8,7 +8,8 @@ import (
"time"
"git.gensokyo.uk/security/fortify/fst"
- "git.gensokyo.uk/security/fortify/internal/app/setuid"
+ "git.gensokyo.uk/security/fortify/internal/app"
+ "git.gensokyo.uk/security/fortify/internal/app/internal/setuid"
"git.gensokyo.uk/security/fortify/internal/sys"
"git.gensokyo.uk/security/fortify/sandbox"
"git.gensokyo.uk/security/fortify/system"
@@ -18,7 +19,7 @@ type sealTestCase struct {
name string
os sys.State
config *fst.Config
- id fst.ID
+ id app.ID
wantSys *system.I
wantContainer *sandbox.Params
}
diff --git a/internal/app/setuid/errors.go b/internal/app/internal/setuid/errors.go
index e6c9685c..fd5acc57 100644
--- a/internal/app/setuid/errors.go
+++ b/internal/app/internal/setuid/errors.go
@@ -4,11 +4,11 @@ import (
"errors"
"log"
- "git.gensokyo.uk/security/fortify/fst"
+ . "git.gensokyo.uk/security/fortify/internal/app"
"git.gensokyo.uk/security/fortify/internal/fmsg"
)
-func PrintRunStateErr(rs *fst.RunState, runErr error) (code int) {
+func PrintRunStateErr(rs *RunState, runErr error) (code int) {
code = rs.ExitStatus()
if runErr != nil {
diff --git a/internal/app/setuid/export_test.go b/internal/app/internal/setuid/export_test.go
index 77182863..d7e2f160 100644
--- a/internal/app/setuid/export_test.go
+++ b/internal/app/internal/setuid/export_test.go
@@ -1,20 +1,20 @@
package setuid
import (
- "git.gensokyo.uk/security/fortify/fst"
+ . "git.gensokyo.uk/security/fortify/internal/app"
"git.gensokyo.uk/security/fortify/internal/sys"
"git.gensokyo.uk/security/fortify/sandbox"
"git.gensokyo.uk/security/fortify/system"
)
-func NewWithID(id fst.ID, os sys.State) fst.App {
+func NewWithID(id ID, os sys.State) App {
a := new(app)
a.id = newID(&id)
a.sys = os
return a
}
-func AppIParams(a fst.App, sa fst.SealedApp) (*system.I, *sandbox.Params) {
+func AppIParams(a App, sa SealedApp) (*system.I, *sandbox.Params) {
v := a.(*app)
seal := sa.(*outcome)
if v.outcome != seal || v.id != seal.id {
diff --git a/internal/app/setuid/process.go b/internal/app/internal/setuid/process.go
index e730225e..271e64e7 100644
--- a/internal/app/setuid/process.go
+++ b/internal/app/internal/setuid/process.go
@@ -12,8 +12,8 @@ import (
"syscall"
"time"
- "git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/internal"
+ . "git.gensokyo.uk/security/fortify/internal/app"
"git.gensokyo.uk/security/fortify/internal/fmsg"
"git.gensokyo.uk/security/fortify/internal/state"
"git.gensokyo.uk/security/fortify/sandbox"
@@ -22,7 +22,7 @@ import (
const shimWaitTimeout = 5 * time.Second
-func (seal *outcome) Run(rs *fst.RunState) error {
+func (seal *outcome) Run(rs *RunState) error {
if !seal.f.CompareAndSwap(false, true) {
// run does much more than just starting a process; calling it twice, even if the first call fails, will result
// in inconsistent state that is impossible to clean up; return here to limit damage and hopefully give the
diff --git a/internal/app/setuid/seal.go b/internal/app/internal/setuid/seal.go
index 92fbc2fa..44a73b1a 100644
--- a/internal/app/setuid/seal.go
+++ b/internal/app/internal/setuid/seal.go
@@ -20,6 +20,8 @@ import (
"git.gensokyo.uk/security/fortify/dbus"
"git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/internal"
+ . "git.gensokyo.uk/security/fortify/internal/app"
+ "git.gensokyo.uk/security/fortify/internal/app/instance/common"
"git.gensokyo.uk/security/fortify/internal/fmsg"
"git.gensokyo.uk/security/fortify/internal/sys"
"git.gensokyo.uk/security/fortify/sandbox"
@@ -64,7 +66,7 @@ var posixUsername = regexp.MustCompilePOSIX("^[a-z_]([A-Za-z0-9_-]{0,31}|[A-Za-z
// outcome stores copies of various parts of [fst.Config]
type outcome struct {
// copied from initialising [app]
- id *stringPair[fst.ID]
+ id *stringPair[ID]
// copied from [sys.State] response
runDirPath string
@@ -95,7 +97,7 @@ type shareHost struct {
runtimeSharePath string
seal *outcome
- sc fst.Paths
+ sc Paths
}
// ensureRuntimeDir must be called if direct access to paths within XDG_RUNTIME_DIR is required
@@ -279,7 +281,7 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
{
var uid, gid int
var err error
- seal.container, seal.env, err = config.Confinement.Sandbox.ToContainer(sys, &uid, &gid)
+ seal.container, seal.env, err = common.NewContainer(config.Confinement.Sandbox, sys, &uid, &gid)
if err != nil {
return fmsg.WrapErrorSuffix(err,
"cannot initialise container configuration:")
diff --git a/internal/app/setuid/shim.go b/internal/app/internal/setuid/shim.go
index 1717f812..1717f812 100644
--- a/internal/app/setuid/shim.go
+++ b/internal/app/internal/setuid/shim.go
diff --git a/internal/app/setuid/strings.go b/internal/app/internal/setuid/strings.go
index f5b51344..6489d6c0 100644
--- a/internal/app/setuid/strings.go
+++ b/internal/app/internal/setuid/strings.go
@@ -3,11 +3,11 @@ package setuid
import (
"strconv"
- "git.gensokyo.uk/security/fortify/fst"
+ . "git.gensokyo.uk/security/fortify/internal/app"
)
-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()} }
+func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} }
+func newID(id *ID) *stringPair[ID] { return &stringPair[ID]{*id, id.String()} }
// stringPair stores a value and its string representation.
type stringPair[T comparable] struct {
diff --git a/internal/state/multi.go b/internal/state/multi.go
index 58b223c1..f4436112 100644
--- a/internal/state/multi.go
+++ b/internal/state/multi.go
@@ -14,6 +14,7 @@ import (
"syscall"
"git.gensokyo.uk/security/fortify/fst"
+ "git.gensokyo.uk/security/fortify/internal/app"
"git.gensokyo.uk/security/fortify/internal/fmsg"
)
@@ -129,7 +130,7 @@ type multiBackend struct {
lock sync.RWMutex
}
-func (b *multiBackend) filename(id *fst.ID) string {
+func (b *multiBackend) filename(id *app.ID) string {
return path.Join(b.path, id.String())
}
@@ -189,8 +190,8 @@ func (b *multiBackend) load(decode bool) (Entries, error) {
return nil, fmt.Errorf("unexpected directory %q in store", e.Name())
}
- id := new(fst.ID)
- if err := fst.ParseAppID(id, e.Name()); err != nil {
+ id := new(app.ID)
+ if err := app.ParseAppID(id, e.Name()); err != nil {
return nil, err
}
@@ -335,7 +336,7 @@ func (b *multiBackend) encodeState(w io.WriteSeeker, state *State, configWriter
return err
}
-func (b *multiBackend) Destroy(id fst.ID) error {
+func (b *multiBackend) Destroy(id app.ID) error {
b.lock.Lock()
defer b.lock.Unlock()
diff --git a/internal/state/state.go b/internal/state/state.go
index 6e76d517..609b4e92 100644
--- a/internal/state/state.go
+++ b/internal/state/state.go
@@ -6,11 +6,12 @@ import (
"time"
"git.gensokyo.uk/security/fortify/fst"
+ "git.gensokyo.uk/security/fortify/internal/app"
)
var ErrNoConfig = errors.New("state does not contain config")
-type Entries map[fst.ID]*State
+type Entries map[app.ID]*State
type Store interface {
// Do calls f exactly once and ensures store exclusivity until f returns.
@@ -29,7 +30,7 @@ type Store interface {
// Cursor provides access to the store
type Cursor interface {
Save(state *State, configWriter io.WriterTo) error
- Destroy(id fst.ID) error
+ Destroy(id app.ID) error
Load() (Entries, error)
Len() (int, error)
}
@@ -37,7 +38,7 @@ type Cursor interface {
// State is a fortify process's state
type State struct {
// fortify instance id
- ID fst.ID `json:"instance"`
+ ID app.ID `json:"instance"`
// child process PID value
PID int `json:"pid"`
// sealed app configuration
diff --git a/internal/state/state_test.go b/internal/state/state_test.go
index 22fd3cd6..fe7a6cc6 100644
--- a/internal/state/state_test.go
+++ b/internal/state/state_test.go
@@ -11,6 +11,7 @@ import (
"time"
"git.gensokyo.uk/security/fortify/fst"
+ "git.gensokyo.uk/security/fortify/internal/app"
"git.gensokyo.uk/security/fortify/internal/state"
)
@@ -133,7 +134,7 @@ func testStore(t *testing.T, s state.Store) {
}
func makeState(t *testing.T, s *state.State, ct io.Writer) {
- if err := fst.NewAppID(&s.ID); err != nil {
+ if err := app.NewAppID(&s.ID); err != nil {
t.Fatalf("cannot create dummy state: %v", err)
}
if err := gob.NewEncoder(ct).Encode(fst.Template()); err != nil {
diff --git a/internal/sys/interface.go b/internal/sys/interface.go
index 88afd670..ba8e0bf4 100644
--- a/internal/sys/interface.go
+++ b/internal/sys/interface.go
@@ -6,7 +6,7 @@ import (
"path"
"strconv"
- "git.gensokyo.uk/security/fortify/fst"
+ "git.gensokyo.uk/security/fortify/internal/app"
"git.gensokyo.uk/security/fortify/internal/fmsg"
)
@@ -41,14 +41,14 @@ type State interface {
Printf(format string, v ...any)
// Paths returns a populated [Paths] struct.
- Paths() fst.Paths
+ Paths() app.Paths
// Uid invokes fsu and returns target uid.
// Any errors returned by Uid is already wrapped [fmsg.BaseError].
Uid(aid int) (int, error)
}
// CopyPaths is a generic implementation of [fst.Paths].
-func CopyPaths(os State, v *fst.Paths) {
+func CopyPaths(os State, v *app.Paths) {
v.SharePath = path.Join(os.TempDir(), "fortify."+strconv.Itoa(os.Getuid()))
fmsg.Verbosef("process share directory at %q", v.SharePath)
diff --git a/internal/sys/std.go b/internal/sys/std.go
index 5e63396b..1b235795 100644
--- a/internal/sys/std.go
+++ b/internal/sys/std.go
@@ -12,15 +12,15 @@ import (
"sync"
"syscall"
- "git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/internal"
+ "git.gensokyo.uk/security/fortify/internal/app"
"git.gensokyo.uk/security/fortify/internal/fmsg"
"git.gensokyo.uk/security/fortify/sandbox"
)
// Std implements System using the standard library.
type Std struct {
- paths fst.Paths
+ paths app.Paths
pathsOnce sync.Once
uidOnce sync.Once
@@ -48,7 +48,7 @@ func (s *Std) Printf(format string, v ...any) { fmsg.Verbosef(form
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
-func (s *Std) Paths() fst.Paths {
+func (s *Std) Paths() app.Paths {
s.pathsOnce.Do(func() { CopyPaths(s, &s.paths) })
return s.paths
}