app/instance: wrap internal implementation
All checks were successful
Test / Create distribution (push) Successful in 26s
Test / Sandbox (push) Successful in 1m44s
Test / Fortify (push) Successful in 2m37s
Test / Sandbox (race detector) (push) Successful in 2m59s
Test / Fpkg (push) Successful in 3m34s
Test / Fortify (race detector) (push) Successful in 4m6s
Test / Flake checks (push) Successful in 59s
All checks were successful
Test / Create distribution (push) Successful in 26s
Test / Sandbox (push) Successful in 1m44s
Test / Fortify (push) Successful in 2m37s
Test / Sandbox (race detector) (push) Successful in 2m59s
Test / Fpkg (push) Successful in 3m34s
Test / Fortify (race detector) (push) Successful in 4m6s
Test / Flake checks (push) Successful in 59s
This reduces the scope of the fst package, which was growing questionably large. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
59
internal/app/app.go
Normal file
59
internal/app/app.go
Normal file
@@ -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"`
|
||||
}
|
||||
48
internal/app/id.go
Normal file
48
internal/app/id.go
Normal file
@@ -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
|
||||
}
|
||||
63
internal/app/id_test.go
Normal file
63
internal/app/id_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
187
internal/app/instance/common/container.go
Normal file
187
internal/app/instance/common/container.go
Normal file
@@ -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
|
||||
}
|
||||
11
internal/app/instance/common/path.go
Normal file
11
internal/app/instance/common/path.go
Normal file
@@ -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
|
||||
}
|
||||
85
internal/app/instance/common/path_test.go
Normal file
85
internal/app/instance/common/path_test.go
Normal file
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
17
internal/app/instance/errors.go
Normal file
17
internal/app/instance/errors.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
33
internal/app/instance/new.go
Normal file
33
internal/app/instance/new.go
Normal file
@@ -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
|
||||
}
|
||||
6
internal/app/instance/shim.go
Normal file
6
internal/app/instance/shim.go
Normal file
@@ -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() }
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
@@ -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",
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -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 {
|
||||
@@ -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
|
||||
@@ -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:")
|
||||
@@ -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 {
|
||||
Reference in New Issue
Block a user