aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-02-19 13:41:06 +0900
committerOphestra <cat@gensokyo.uk>2025-02-19 16:20:25 +0900
commitef81828e0c1ce6277e0b124f6062eab564dfeceb (patch)
tree35a127281d3e75d9a71d070133ce3ed26644a40e /internal
parent2978a6f046f52861a7ac749d1e414758caab8bd5 (diff)
app: remove share method
This is yet another implementation detail from before system.I, getting rid of this vastly cuts down on redundant seal state. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal')
-rw-r--r--internal/app/app_nixos_test.go12
-rw-r--r--internal/app/app_pd_test.go18
-rw-r--r--internal/app/seal.go476
-rw-r--r--internal/app/share.go339
4 files changed, 374 insertions, 471 deletions
diff --git a/internal/app/app_nixos_test.go b/internal/app/app_nixos_test.go
index 02d9aa27..7ebd32e9 100644
--- a/internal/app/app_nixos_test.go
+++ b/internal/app/app_nixos_test.go
@@ -18,13 +18,13 @@ var testCasesNixos = []sealTestCase{
AppID: 1, Groups: []string{}, Username: "u0_a1",
Outer: "/var/lib/persist/module/fortify/0/1",
Sandbox: &fst.SandboxConfig{
- UserNS: true, Net: true, MapRealUID: true, DirectWayland: true, Env: nil,
+ UserNS: true, Net: true, MapRealUID: true, DirectWayland: true, Env: nil, AutoEtc: true,
Filesystem: []*fst.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"},
{Src: "/run/opengl-driver", Must: true}, {Src: "/dev/dri", Device: true},
- }, AutoEtc: true,
+ },
Override: []string{"/var/run/nscd"},
},
SystemBus: &dbus.Config{
@@ -56,12 +56,12 @@ var testCasesNixos = []sealTestCase{
},
system.New(1000001).
Ensure("/tmp/fortify.1971", 0711).
- Ephemeral(system.Process, "/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1", 0711).
- Ensure("/tmp/fortify.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir", acl.Execute).
- Ensure("/tmp/fortify.1971/tmpdir/1", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/1", acl.Read, acl.Write, acl.Execute).
Ensure("/run/user/1971/fortify", 0700).UpdatePermType(system.User, "/run/user/1971/fortify", acl.Execute).
Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
+ Ephemeral(system.Process, "/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1", 0711).
Ephemeral(system.Process, "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1", acl.Execute).
+ Ensure("/tmp/fortify.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir", acl.Execute).
+ Ensure("/tmp/fortify.1971/tmpdir/1", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/1", acl.Read, acl.Write, acl.Execute).
UpdatePermType(system.EWayland, "/run/user/1971/wayland-0", acl.Read, acl.Write, acl.Execute).
Link("/run/user/1971/pulse/native", "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse").
CopyFile(nil, "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
@@ -205,9 +205,9 @@ var testCasesNixos = []sealTestCase{
Symlink(fst.Tmp+"/etc/zprofile", "/etc/zprofile").
Symlink(fst.Tmp+"/etc/zshenv", "/etc/zshenv").
Symlink(fst.Tmp+"/etc/zshrc", "/etc/zshrc").
- Bind("/tmp/fortify.1971/tmpdir/1", "/tmp", false, true).
Tmpfs("/run/user", 1048576).
Tmpfs("/run/user/1971", 8388608).
+ Bind("/tmp/fortify.1971/tmpdir/1", "/tmp", false, true).
Bind("/var/lib/persist/module/fortify/0/1", "/var/lib/persist/module/fortify/0/1", false, true).
CopyBind("/etc/passwd", []byte("u0_a1:x:1971:1971:Fortify:/var/lib/persist/module/fortify/0/1:/run/current-system/sw/bin/zsh\n")).
CopyBind("/etc/group", []byte("fortify:x:1971:\n")).
diff --git a/internal/app/app_pd_test.go b/internal/app/app_pd_test.go
index fb74ae10..2a03d356 100644
--- a/internal/app/app_pd_test.go
+++ b/internal/app/app_pd_test.go
@@ -29,12 +29,12 @@ var testCasesPd = []sealTestCase{
},
system.New(1000000).
Ensure("/tmp/fortify.1971", 0711).
- Ephemeral(system.Process, "/tmp/fortify.1971/4a450b6596d7bc15bd01780eb9a607ac", 0711).
- Ensure("/tmp/fortify.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir", acl.Execute).
- Ensure("/tmp/fortify.1971/tmpdir/0", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/0", acl.Read, acl.Write, acl.Execute).
Ensure("/run/user/1971/fortify", 0700).UpdatePermType(system.User, "/run/user/1971/fortify", acl.Execute).
Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
- Ephemeral(system.Process, "/run/user/1971/fortify/4a450b6596d7bc15bd01780eb9a607ac", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/4a450b6596d7bc15bd01780eb9a607ac", acl.Execute),
+ Ephemeral(system.Process, "/tmp/fortify.1971/4a450b6596d7bc15bd01780eb9a607ac", 0711).
+ Ephemeral(system.Process, "/run/user/1971/fortify/4a450b6596d7bc15bd01780eb9a607ac", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/4a450b6596d7bc15bd01780eb9a607ac", acl.Execute).
+ Ensure("/tmp/fortify.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir", acl.Execute).
+ Ensure("/tmp/fortify.1971/tmpdir/0", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/0", acl.Read, acl.Write, acl.Execute),
(&bwrap.Config{
Net: true,
UserNS: true,
@@ -150,9 +150,9 @@ var testCasesPd = []sealTestCase{
Symlink(fst.Tmp+"/etc/zprofile", "/etc/zprofile").
Symlink(fst.Tmp+"/etc/zshenv", "/etc/zshenv").
Symlink(fst.Tmp+"/etc/zshrc", "/etc/zshrc").
- Bind("/tmp/fortify.1971/tmpdir/0", "/tmp", false, true).
Tmpfs("/run/user", 1048576).
Tmpfs("/run/user/65534", 8388608).
+ Bind("/tmp/fortify.1971/tmpdir/0", "/tmp", false, true).
Bind("/home/chronos", "/home/chronos", false, true).
CopyBind("/etc/passwd", []byte("chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n")).
CopyBind("/etc/group", []byte("fortify:x:65534:\n")).
@@ -212,12 +212,12 @@ var testCasesPd = []sealTestCase{
},
system.New(1000009).
Ensure("/tmp/fortify.1971", 0711).
- Ephemeral(system.Process, "/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c", 0711).
- Ensure("/tmp/fortify.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir", acl.Execute).
- Ensure("/tmp/fortify.1971/tmpdir/9", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/9", acl.Read, acl.Write, acl.Execute).
Ensure("/run/user/1971/fortify", 0700).UpdatePermType(system.User, "/run/user/1971/fortify", acl.Execute).
Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
+ Ephemeral(system.Process, "/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c", 0711).
Ephemeral(system.Process, "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c", acl.Execute).
+ Ensure("/tmp/fortify.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir", acl.Execute).
+ Ensure("/tmp/fortify.1971/tmpdir/9", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/9", acl.Read, acl.Write, acl.Execute).
Ensure("/tmp/fortify.1971/wayland", 0711).
Wayland(new(*os.File), "/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/1971/wayland-0", "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c").
Link("/run/user/1971/pulse/native", "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse").
@@ -376,9 +376,9 @@ var testCasesPd = []sealTestCase{
Symlink(fst.Tmp+"/etc/zprofile", "/etc/zprofile").
Symlink(fst.Tmp+"/etc/zshenv", "/etc/zshenv").
Symlink(fst.Tmp+"/etc/zshrc", "/etc/zshrc").
- Bind("/tmp/fortify.1971/tmpdir/9", "/tmp", false, true).
Tmpfs("/run/user", 1048576).
Tmpfs("/run/user/65534", 8388608).
+ Bind("/tmp/fortify.1971/tmpdir/9", "/tmp", false, true).
Bind("/home/chronos", "/home/chronos", false, true).
CopyBind("/etc/passwd", []byte("chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n")).
CopyBind("/etc/group", []byte("fortify:x:65534:\n")).
diff --git a/internal/app/seal.go b/internal/app/seal.go
index 34879637..23f1c0c0 100644
--- a/internal/app/seal.go
+++ b/internal/app/seal.go
@@ -10,6 +10,7 @@ import (
"os"
"path"
"regexp"
+ "strings"
"git.gensokyo.uk/security/fortify/acl"
"git.gensokyo.uk/security/fortify/dbus"
@@ -20,6 +21,26 @@ import (
"git.gensokyo.uk/security/fortify/internal/state"
"git.gensokyo.uk/security/fortify/internal/sys"
"git.gensokyo.uk/security/fortify/system"
+ "git.gensokyo.uk/security/fortify/wl"
+)
+
+const (
+ home = "HOME"
+ shell = "SHELL"
+
+ xdgConfigHome = "XDG_CONFIG_HOME"
+ xdgRuntimeDir = "XDG_RUNTIME_DIR"
+ xdgSessionClass = "XDG_SESSION_CLASS"
+ xdgSessionType = "XDG_SESSION_TYPE"
+
+ term = "TERM"
+ display = "DISPLAY"
+
+ pulseServer = "PULSE_SERVER"
+ pulseCookie = "PULSE_COOKIE"
+
+ dbusSessionBusAddress = "DBUS_SESSION_BUS_ADDRESS"
+ dbusSystemBusAddress = "DBUS_SYSTEM_BUS_ADDRESS"
)
var (
@@ -27,65 +48,38 @@ var (
ErrUser = errors.New("invalid aid")
ErrHome = errors.New("invalid home directory")
ErrName = errors.New("invalid username")
+
+ ErrXDisplay = errors.New(display + " unset")
+
+ ErrPulseCookie = errors.New("pulse cookie not present")
+ ErrPulseSocket = errors.New("pulse socket not present")
+ ErrPulseMode = errors.New("unexpected pulse socket mode")
)
var posixUsername = regexp.MustCompilePOSIX("^[a-z_]([A-Za-z0-9_-]{0,31}|[A-Za-z0-9_-]{0,30}\\$)$")
// appSeal stores copies of various parts of [fst.Config]
type appSeal struct {
- // string representation of [fst.ID]
- id string
- // dump dbus proxy message buffer
- dbusMsg func()
-
- // reverse-DNS style arbitrary identifier string from config;
- // passed to wayland security-context-v1 as application ID
- // and used as part of defaults in dbus session proxy
- appID string
- // final argv, passed to init
+ // passed through from [fst.Config]
command []string
// state instance initialised during seal; used during process lifecycle events
store state.Store
+ // initial [fst.Config] gob stream for state data;
+ // this is prepared ahead of time as config is mutated during seal creation
+ ct io.WriterTo
+ // dump dbus proxy message buffer
+ dbusMsg func()
// whether [system.I] was committed; used during process lifecycle events
needRevert bool
// whether state was inserted into [state.Store]; used during process lifecycle events
stateInStore bool
- // process-specific share directory path ([os.TempDir])
- share string
- // process-specific share directory path ([fst.Paths] XDG_RUNTIME_DIR)
- shareLocal string
-
- // initial [fst.Config] gob stream for state data;
- // this is prepared ahead of time as config is mutated during seal creation
- ct io.WriterTo
- // passed through from [fst.SandboxConfig];
- // when this gets set no attempt is made to attach security-context-v1
- // and the bare socket is mounted to the sandbox
- directWayland bool
- // mount tmpfs over these paths, runs right before extraPerms
- override []string
- // extra [acl.Update] ops, appended at the end of [system.I]
- extraPerms []*sealedExtraPerm
-
- // post fsu state
- user appUser
- // inner XDG_RUNTIME_DIR, default formatting via user
- innerRuntimeDir string
- // mapped uid and gid in user namespace
- mapuid *stringPair[int]
-
+ user appUser
sys *system.I
container *bwrap.Config
bwrapSync *os.File
- // prevents sharing from happening twice
- shared bool
-
- system.Enablements
- fst.Paths
-
// protected by upstream mutex
}
@@ -107,45 +101,30 @@ type appUser struct {
username string
}
-type sealedExtraPerm struct {
- name string
- perms acl.Perms
- ensure bool
-}
-
func (seal *appSeal) finalise(sys sys.State, config *fst.Config, id string) error {
- // encode initial configuration for state tracking
- ct := new(bytes.Buffer)
- if err := gob.NewEncoder(ct).Encode(config); err != nil {
- return fmsg.WrapErrorSuffix(err,
- "cannot encode initial config:")
- }
- seal.ct = ct
-
- seal.Paths = sys.Paths()
-
- // pass through config values
- seal.id = id
- seal.appID = config.ID
- seal.command = config.Command
-
{
- // mapped uid defaults to 65534 to work around file ownership checks due to a bwrap limitation
- mapuid := 65534
- if config.Confinement.Sandbox != nil && config.Confinement.Sandbox.MapRealUID {
- // some programs fail to connect to dbus session running as a different uid, so a
- // separate workaround is introduced to map priv-side caller uid in namespace
- mapuid = sys.Geteuid()
+ // encode initial configuration for state tracking
+ ct := new(bytes.Buffer)
+ if err := gob.NewEncoder(ct).Encode(config); err != nil {
+ return fmsg.WrapErrorSuffix(err,
+ "cannot encode initial config:")
}
- seal.mapuid = newInt(mapuid)
- seal.innerRuntimeDir = path.Join("/run/user", seal.mapuid.String())
+ seal.ct = ct
}
- // validate uid and set user info
+ // pass through command slice; this value is never touched in the main process
+ seal.command = config.Command
+
+ // allowed aid range 0 to 9999, this is checked again in fsu
if config.Confinement.AppID < 0 || config.Confinement.AppID > 9999 {
return fmsg.WrapError(ErrUser,
fmt.Sprintf("aid %d out of range", config.Confinement.AppID))
}
+
+ /*
+ Resolve post-fsu user state
+ */
+
seal.user = appUser{
aid: newInt(config.Confinement.AppID),
data: config.Confinement.Outer,
@@ -166,15 +145,11 @@ func (seal *appSeal) finalise(sys sys.State, config *fst.Config, id string) erro
if seal.user.home == "" {
seal.user.home = seal.user.data
}
-
- // invoke fsu for full uid
if u, err := sys.Uid(seal.user.aid.unwrap()); err != nil {
return err
} else {
seal.user.uid = newInt(u)
}
-
- // resolve supplementary group ids from names
seal.user.supp = make([]string, len(config.Confinement.Groups))
for i, name := range config.Confinement.Groups {
if g, err := sys.LookupGroup(name); err != nil {
@@ -185,33 +160,14 @@ func (seal *appSeal) finalise(sys sys.State, config *fst.Config, id string) erro
}
}
- // build extra perms
- seal.extraPerms = make([]*sealedExtraPerm, len(config.Confinement.ExtraPerms))
- for i, p := range config.Confinement.ExtraPerms {
- if p == nil {
- continue
- }
-
- seal.extraPerms[i] = new(sealedExtraPerm)
- seal.extraPerms[i].name = p.Path
- seal.extraPerms[i].perms = make(acl.Perms, 0, 3)
- if p.Read {
- seal.extraPerms[i].perms = append(seal.extraPerms[i].perms, acl.Read)
- }
- if p.Write {
- seal.extraPerms[i].perms = append(seal.extraPerms[i].perms, acl.Write)
- }
- if p.Execute {
- seal.extraPerms[i].perms = append(seal.extraPerms[i].perms, acl.Execute)
- }
- seal.extraPerms[i].ensure = p.Ensure
- }
+ /*
+ Resolve initial container state
+ */
- // map sandbox config to bwrap
+ // permissive defaults
if config.Confinement.Sandbox == nil {
fmsg.Verbose("sandbox configuration not supplied, PROCEED WITH CAUTION")
- // permissive defaults
conf := &fst.SandboxConfig{
UserNS: true,
Net: true,
@@ -254,40 +210,326 @@ func (seal *appSeal) finalise(sys sys.State, config *fst.Config, id string) erro
config.Confinement.Sandbox = conf
}
- seal.directWayland = config.Confinement.Sandbox.DirectWayland
- if b, err := config.Confinement.Sandbox.Bwrap(sys); err != nil {
- return err
- } else {
- seal.container = b
- }
- seal.override = config.Confinement.Sandbox.Override
- if seal.container.SetEnv == nil {
- seal.container.SetEnv = make(map[string]string)
+
+ var mapuid *stringPair[int]
+ {
+ var uid int
+ var err error
+ seal.container, err = config.Confinement.Sandbox.Bwrap(sys, &uid)
+ if err != nil {
+ return err
+ }
+ mapuid = newInt(uid)
+ if seal.container.SetEnv == nil {
+ seal.container.SetEnv = make(map[string]string)
+ }
}
- // open process state store
- // the simple store only starts holding an open file after first action
- // store activity begins after Start is called and must end before Wait
- seal.store = state.NewMulti(seal.RunDirPath)
+ /*
+ Initialise externals
+ */
- // initialise system interface with os uid
+ sc := sys.Paths()
+ seal.store = state.NewMulti(sc.RunDirPath)
seal.sys = system.New(seal.user.uid.unwrap())
seal.sys.IsVerbose = fmsg.Load
seal.sys.Verbose = fmsg.Verbose
seal.sys.Verbosef = fmsg.Verbosef
seal.sys.WrapErr = fmsg.WrapError
- // pass through enablements
- seal.Enablements = config.Confinement.Enablements
+ /*
+ Work directories
+ */
- // this method calls all share methods in sequence
- if err := seal.setupShares([2]*dbus.Config{config.Confinement.SessionBus, config.Confinement.SystemBus}, sys); err != nil {
- return err
+ // base fortify share path
+ seal.sys.Ensure(sc.SharePath, 0711)
+
+ // outer paths used by the main process
+ seal.sys.Ensure(sc.RunDirPath, 0700)
+ seal.sys.UpdatePermType(system.User, sc.RunDirPath, acl.Execute)
+ seal.sys.Ensure(sc.RuntimePath, 0700) // ensure this dir in case XDG_RUNTIME_DIR is unset
+ seal.sys.UpdatePermType(system.User, sc.RuntimePath, acl.Execute)
+
+ // outer process-specific share directory
+ sharePath := path.Join(sc.SharePath, id)
+ seal.sys.Ephemeral(system.Process, sharePath, 0711)
+ // similar to share but within XDG_RUNTIME_DIR
+ sharePathLocal := path.Join(sc.RunDirPath, id)
+ seal.sys.Ephemeral(system.Process, sharePathLocal, 0700)
+ seal.sys.UpdatePerm(sharePathLocal, acl.Execute)
+
+ // inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` as post-fsu user
+ innerRuntimeDir := path.Join("/run/user", mapuid.String())
+ seal.container.Tmpfs("/run/user", 1*1024*1024)
+ seal.container.Tmpfs(innerRuntimeDir, 8*1024*1024)
+ seal.container.SetEnv[xdgRuntimeDir] = innerRuntimeDir
+ seal.container.SetEnv[xdgSessionClass] = "user"
+ seal.container.SetEnv[xdgSessionType] = "tty"
+
+ // outer path for inner /tmp
+ {
+ tmpdir := path.Join(sc.SharePath, "tmpdir")
+ seal.sys.Ensure(tmpdir, 0700)
+ seal.sys.UpdatePermType(system.User, tmpdir, acl.Execute)
+ tmpdirProc := path.Join(tmpdir, seal.user.aid.String())
+ seal.sys.Ensure(tmpdirProc, 01700)
+ seal.sys.UpdatePermType(system.User, tmpdirProc, acl.Read, acl.Write, acl.Execute)
+ seal.container.Bind(tmpdirProc, "/tmp", false, true)
+ }
+
+ /*
+ Passwd database
+ */
+
+ // look up shell
+ sh := "/bin/sh"
+ if s, ok := sys.LookupEnv(shell); ok {
+ seal.container.SetEnv[shell] = s
+ sh = s
+ }
+
+ // bind home directory
+ homeDir := "/var/empty"
+ if seal.user.home != "" {
+ homeDir = seal.user.home
+ }
+ username := "chronos"
+ if seal.user.username != "" {
+ username = seal.user.username
+ }
+ seal.container.Bind(seal.user.data, homeDir, false, true)
+ seal.container.Chdir = homeDir
+ seal.container.SetEnv["HOME"] = homeDir
+ seal.container.SetEnv["USER"] = username
+
+ // generate /etc/passwd and /etc/group
+ seal.container.CopyBind("/etc/passwd",
+ []byte(username+":x:"+mapuid.String()+":"+mapuid.String()+":Fortify:"+homeDir+":"+sh+"\n"))
+ seal.container.CopyBind("/etc/group",
+ []byte("fortify:x:"+mapuid.String()+":\n"))
+
+ /*
+ Display servers
+ */
+
+ // pass $TERM to launcher
+ if t, ok := sys.LookupEnv(term); ok {
+ seal.container.SetEnv[term] = t
+ }
+
+ // set up wayland
+ if config.Confinement.Enablements.Has(system.EWayland) {
+ // outer wayland socket (usually `/run/user/%d/wayland-%d`)
+ var socketPath string
+ if name, ok := sys.LookupEnv(wl.WaylandDisplay); !ok {
+ fmsg.Verbose(wl.WaylandDisplay + " is not set, assuming " + wl.FallbackName)
+ socketPath = path.Join(sc.RuntimePath, wl.FallbackName)
+ } else if !path.IsAbs(name) {
+ socketPath = path.Join(sc.RuntimePath, name)
+ } else {
+ socketPath = name
+ }
+
+ innerPath := path.Join(innerRuntimeDir, wl.FallbackName)
+ seal.container.SetEnv[wl.WaylandDisplay] = wl.FallbackName
+
+ if !config.Confinement.Sandbox.DirectWayland { // set up security-context-v1
+ socketDir := path.Join(sc.SharePath, "wayland")
+ outerPath := path.Join(socketDir, id)
+ seal.sys.Ensure(socketDir, 0711)
+ appID := config.ID
+ if appID == "" {
+ // use instance ID in case app id is not set
+ appID = "uk.gensokyo.fortify." + id
+ }
+ seal.sys.Wayland(&seal.bwrapSync, outerPath, socketPath, appID, id)
+ seal.container.Bind(outerPath, innerPath)
+ } else { // bind mount wayland socket (insecure)
+ fmsg.Verbose("direct wayland access, PROCEED WITH CAUTION")
+ seal.container.Bind(socketPath, innerPath)
+ seal.sys.UpdatePermType(system.EWayland, socketPath, acl.Read, acl.Write, acl.Execute)
+ }
+ }
+
+ // set up X11
+ if config.Confinement.Enablements.Has(system.EX11) {
+ // discover X11 and grant user permission via the `ChangeHosts` command
+ if d, ok := sys.LookupEnv(display); !ok {
+ return fmsg.WrapError(ErrXDisplay,
+ "DISPLAY is not set")
+ } else {
+ seal.sys.ChangeHosts("#" + seal.user.uid.String())
+ seal.container.SetEnv[display] = d
+ seal.container.Bind("/tmp/.X11-unix", "/tmp/.X11-unix")
+ }
+ }
+
+ /*
+ PulseAudio server and authentication
+ */
+
+ if config.Confinement.Enablements.Has(system.EPulse) {
+ // PulseAudio runtime directory (usually `/run/user/%d/pulse`)
+ pulseRuntimeDir := path.Join(sc.RuntimePath, "pulse")
+ // PulseAudio socket (usually `/run/user/%d/pulse/native`)
+ pulseSocket := path.Join(pulseRuntimeDir, "native")
+
+ if _, err := sys.Stat(pulseRuntimeDir); err != nil {
+ if !errors.Is(err, fs.ErrNotExist) {
+ return fmsg.WrapErrorSuffix(err,
+ fmt.Sprintf("cannot access PulseAudio directory %q:", pulseRuntimeDir))
+ }
+ return fmsg.WrapError(ErrPulseSocket,
+ fmt.Sprintf("PulseAudio directory %q not found", pulseRuntimeDir))
+ }
+
+ if s, err := sys.Stat(pulseSocket); err != nil {
+ if !errors.Is(err, fs.ErrNotExist) {
+ return fmsg.WrapErrorSuffix(err,
+ fmt.Sprintf("cannot access PulseAudio socket %q:", pulseSocket))
+ }
+ return fmsg.WrapError(ErrPulseSocket,
+ fmt.Sprintf("PulseAudio directory %q found but socket does not exist", pulseRuntimeDir))
+ } else {
+ if m := s.Mode(); m&0o006 != 0o006 {
+ return fmsg.WrapError(ErrPulseMode,
+ fmt.Sprintf("unexpected permissions on %q:", pulseSocket), m)
+ }
+ }
+
+ // hard link pulse socket into target-executable share
+ innerPulseRuntimeDir := path.Join(sharePathLocal, "pulse")
+ innerPulseSocket := path.Join(innerRuntimeDir, "pulse", "native")
+ seal.sys.Link(pulseSocket, innerPulseRuntimeDir)
+ seal.container.Bind(innerPulseRuntimeDir, innerPulseSocket)
+ seal.container.SetEnv[pulseServer] = "unix:" + innerPulseSocket
+
+ // publish current user's pulse cookie for target user
+ if src, err := discoverPulseCookie(sys); err != nil {
+ // not fatal
+ fmsg.Verbose(strings.TrimSpace(err.(*fmsg.BaseError).Message()))
+ } else {
+ innerDst := fst.Tmp + "/pulse-cookie"
+ seal.container.SetEnv[pulseCookie] = innerDst
+ payload := new([]byte)
+ seal.container.CopyBindRef(innerDst, &payload)
+ seal.sys.CopyFile(payload, src, 256, 256)
+ }
+ }
+
+ /*
+ D-Bus proxy
+ */
+
+ if config.Confinement.Enablements.Has(system.EDBus) {
+ // ensure dbus session bus defaults
+ if config.Confinement.SessionBus == nil {
+ config.Confinement.SessionBus = dbus.NewConfig(config.ID, true, true)
+ }
+
+ // downstream socket paths
+ sessionPath, systemPath := path.Join(sharePath, "bus"), path.Join(sharePath, "system_bus_socket")
+
+ // configure dbus proxy
+ if f, err := seal.sys.ProxyDBus(
+ config.Confinement.SessionBus, config.Confinement.SystemBus,
+ sessionPath, systemPath,
+ ); err != nil {
+ return err
+ } else {
+ seal.dbusMsg = f
+ }
+
+ // share proxy sockets
+ sessionInner := path.Join(innerRuntimeDir, "bus")
+ seal.container.SetEnv[dbusSessionBusAddress] = "unix:path=" + sessionInner
+ seal.container.Bind(sessionPath, sessionInner)
+ seal.sys.UpdatePerm(sessionPath, acl.Read, acl.Write)
+ if config.Confinement.SystemBus != nil {
+ systemInner := "/run/dbus/system_bus_socket"
+ seal.container.SetEnv[dbusSystemBusAddress] = "unix:path=" + systemInner
+ seal.container.Bind(systemPath, systemInner)
+ seal.sys.UpdatePerm(systemPath, acl.Read, acl.Write)
+ }
}
- // verbose log seal information
+ /*
+ Miscellaneous
+ */
+
+ // queue overriding tmpfs at the end of seal.container.Filesystem
+ for _, dest := range config.Confinement.Sandbox.Override {
+ seal.container.Tmpfs(dest, 8*1024)
+ }
+
+ // append ExtraPerms last
+ for _, p := range config.Confinement.ExtraPerms {
+ if p == nil {
+ continue
+ }
+
+ if p.Ensure {
+ seal.sys.Ensure(p.Path, 0700)
+ }
+
+ perms := make(acl.Perms, 0, 3)
+ if p.Read {
+ perms = append(perms, acl.Read)
+ }
+ if p.Write {
+ perms = append(perms, acl.Write)
+ }
+ if p.Execute {
+ perms = append(perms, acl.Execute)
+ }
+ seal.sys.UpdatePermType(system.User, p.Path, perms...)
+ }
+
+ // mount fortify in sandbox for init
+ seal.container.Bind(sys.MustExecutable(), path.Join(fst.Tmp, "sbin/fortify"))
+ seal.container.Symlink("fortify", path.Join(fst.Tmp, "sbin/init"))
+
fmsg.Verbosef("created application seal for uid %s (%s) groups: %v, command: %s",
seal.user.uid, seal.user.username, config.Confinement.Groups, config.Command)
return nil
}
+
+// discoverPulseCookie attempts various standard methods to discover the current user's PulseAudio authentication cookie
+func discoverPulseCookie(sys sys.State) (string, error) {
+ if p, ok := sys.LookupEnv(pulseCookie); ok {
+ return p, nil
+ }
+
+ // dotfile $HOME/.pulse-cookie
+ if p, ok := sys.LookupEnv(home); ok {
+ p = path.Join(p, ".pulse-cookie")
+ if s, err := sys.Stat(p); err != nil {
+ if !errors.Is(err, fs.ErrNotExist) {
+ return p, fmsg.WrapErrorSuffix(err,
+ fmt.Sprintf("cannot access PulseAudio cookie %q:", p))
+ }
+ // not found, try next method
+ } else if !s.IsDir() {
+ return p, nil
+ }
+ }
+
+ // $XDG_CONFIG_HOME/pulse/cookie
+ if p, ok := sys.LookupEnv(xdgConfigHome); ok {
+ p = path.Join(p, "pulse", "cookie")
+ if s, err := sys.Stat(p); err != nil {
+ if !errors.Is(err, fs.ErrNotExist) {
+ return p, fmsg.WrapErrorSuffix(err,
+ fmt.Sprintf("cannot access PulseAudio cookie %q:", p))
+ }
+ // not found, try next method
+ } else if !s.IsDir() {
+ return p, nil
+ }
+ }
+
+ return "", fmsg.WrapError(ErrPulseCookie,
+ fmt.Sprintf("cannot locate PulseAudio cookie (tried $%s, $%s/pulse/cookie, $%s/.pulse-cookie)",
+ pulseCookie, xdgConfigHome, home))
+}
diff --git a/internal/app/share.go b/internal/app/share.go
deleted file mode 100644
index 2094b48b..00000000
--- a/internal/app/share.go
+++ /dev/null
@@ -1,339 +0,0 @@
-package app
-
-import (
- "errors"
- "fmt"
- "io/fs"
- "path"
- "strings"
-
- "git.gensokyo.uk/security/fortify/acl"
- "git.gensokyo.uk/security/fortify/dbus"
- "git.gensokyo.uk/security/fortify/fst"
- "git.gensokyo.uk/security/fortify/internal/fmsg"
- "git.gensokyo.uk/security/fortify/internal/sys"
- "git.gensokyo.uk/security/fortify/system"
- "git.gensokyo.uk/security/fortify/wl"
-)
-
-const (
- home = "HOME"
- shell = "SHELL"
-
- xdgConfigHome = "XDG_CONFIG_HOME"
- xdgRuntimeDir = "XDG_RUNTIME_DIR"
- xdgSessionClass = "XDG_SESSION_CLASS"
- xdgSessionType = "XDG_SESSION_TYPE"
-
- term = "TERM"
- display = "DISPLAY"
-
- pulseServer = "PULSE_SERVER"
- pulseCookie = "PULSE_COOKIE"
-
- dbusSessionBusAddress = "DBUS_SESSION_BUS_ADDRESS"
- dbusSystemBusAddress = "DBUS_SYSTEM_BUS_ADDRESS"
-)
-
-var (
- ErrXDisplay = errors.New(display + " unset")
-
- ErrPulseCookie = errors.New("pulse cookie not present")
- ErrPulseSocket = errors.New("pulse socket not present")
- ErrPulseMode = errors.New("unexpected pulse socket mode")
-)
-
-func (seal *appSeal) setupShares(bus [2]*dbus.Config, os sys.State) error {
- if seal.shared {
- panic("seal shared twice")
- }
- seal.shared = true
-
- /*
- Tmpdir-based share directory
- */
-
- // ensure Share (e.g. `/tmp/fortify.%d`)
- // acl is unnecessary as this directory is world executable
- seal.sys.Ensure(seal.SharePath, 0711)
-
- // ensure process-specific share (e.g. `/tmp/fortify.%d/%s`)
- // acl is unnecessary as this directory is world executable
- seal.share = path.Join(seal.SharePath, seal.id)
- seal.sys.Ephemeral(system.Process, seal.share, 0711)
-
- // ensure child tmpdir parent directory (e.g. `/tmp/fortify.%d/tmpdir`)
- targetTmpdirParent := path.Join(seal.SharePath, "tmpdir")
- seal.sys.Ensure(targetTmpdirParent, 0700)
- seal.sys.UpdatePermType(system.User, targetTmpdirParent, acl.Execute)
-
- // ensure child tmpdir (e.g. `/tmp/fortify.%d/tmpdir/%d`)
- targetTmpdir := path.Join(targetTmpdirParent, seal.user.aid.String())
- seal.sys.Ensure(targetTmpdir, 01700)
- seal.sys.UpdatePermType(system.User, targetTmpdir, acl.Read, acl.Write, acl.Execute)
- seal.container.Bind(targetTmpdir, "/tmp", false, true)
-
- /*
- XDG runtime directory
- */
-
- // mount tmpfs on inner runtime (e.g. `/run/user/%d`)
- seal.container.Tmpfs("/run/user", 1*1024*1024)
- seal.container.Tmpfs(seal.innerRuntimeDir, 8*1024*1024)
-
- // point to inner runtime path `/run/user/%d`
- seal.container.SetEnv[xdgRuntimeDir] = seal.innerRuntimeDir
- seal.container.SetEnv[xdgSessionClass] = "user"
- seal.container.SetEnv[xdgSessionType] = "tty"
-
- // ensure RunDir (e.g. `/run/user/%d/fortify`)
- seal.sys.Ensure(seal.RunDirPath, 0700)
- seal.sys.UpdatePermType(system.User, seal.RunDirPath, acl.Execute)
-
- // ensure runtime directory ACL (e.g. `/run/user/%d`)
- seal.sys.Ensure(seal.RuntimePath, 0700) // ensure this dir in case XDG_RUNTIME_DIR is unset
- seal.sys.UpdatePermType(system.User, seal.RuntimePath, acl.Execute)
-
- // ensure process-specific share local to XDG_RUNTIME_DIR (e.g. `/run/user/%d/fortify/%s`)
- seal.shareLocal = path.Join(seal.RunDirPath, seal.id)
- seal.sys.Ephemeral(system.Process, seal.shareLocal, 0700)
- seal.sys.UpdatePerm(seal.shareLocal, acl.Execute)
-
- /*
- Inner passwd database
- */
-
- // look up shell
- sh := "/bin/sh"
- if s, ok := os.LookupEnv(shell); ok {
- seal.container.SetEnv[shell] = s
- sh = s
- }
-
- // bind home directory
- homeDir := "/var/empty"
- if seal.user.home != "" {
- homeDir = seal.user.home
- }
- username := "chronos"
- if seal.user.username != "" {
- username = seal.user.username
- }
- seal.container.Bind(seal.user.data, homeDir, false, true)
- seal.container.Chdir = homeDir
- seal.container.SetEnv["HOME"] = homeDir
- seal.container.SetEnv["USER"] = username
-
- // generate /etc/passwd and /etc/group
- seal.container.CopyBind("/etc/passwd",
- []byte(username+":x:"+seal.mapuid.String()+":"+seal.mapuid.String()+":Fortify:"+homeDir+":"+sh+"\n"))
- seal.container.CopyBind("/etc/group",
- []byte("fortify:x:"+seal.mapuid.String()+":\n"))
-
- /*
- Display servers
- */
-
- // pass $TERM to launcher
- if t, ok := os.LookupEnv(term); ok {
- seal.container.SetEnv[term] = t
- }
-
- // set up wayland
- if seal.Has(system.EWayland) {
- var socketPath string
- if name, ok := os.LookupEnv(wl.WaylandDisplay); !ok {
- fmsg.Verbose(wl.WaylandDisplay + " is not set, assuming " + wl.FallbackName)
- socketPath = path.Join(seal.RuntimePath, wl.FallbackName)
- } else if !path.IsAbs(name) {
- socketPath = path.Join(seal.RuntimePath, name)
- } else {
- socketPath = name
- }
-
- innerPath := path.Join(seal.innerRuntimeDir, wl.FallbackName)
- seal.container.SetEnv[wl.WaylandDisplay] = wl.FallbackName
-
- if !seal.directWayland { // set up security-context-v1
- socketDir := path.Join(seal.SharePath, "wayland")
- outerPath := path.Join(socketDir, seal.id)
- seal.sys.Ensure(socketDir, 0711)
- appID := seal.appID
- if appID == "" {
- // use instance ID in case app id is not set
- appID = "uk.gensokyo.fortify." + seal.id
- }
- seal.sys.Wayland(&seal.bwrapSync, outerPath, socketPath, appID, seal.id)
- seal.container.Bind(outerPath, innerPath)
- } else { // bind mount wayland socket (insecure)
- fmsg.Verbose("direct wayland access, PROCEED WITH CAUTION")
- seal.container.Bind(socketPath, innerPath)
-
- // ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`)
- seal.sys.UpdatePermType(system.EWayland, socketPath, acl.Read, acl.Write, acl.Execute)
- }
- }
-
- // set up X11
- if seal.Has(system.EX11) {
- // discover X11 and grant user permission via the `ChangeHosts` command
- if d, ok := os.LookupEnv(display); !ok {
- return fmsg.WrapError(ErrXDisplay,
- "DISPLAY is not set")
- } else {
- seal.sys.ChangeHosts("#" + seal.user.uid.String())
- seal.container.SetEnv[display] = d
- seal.container.Bind("/tmp/.X11-unix", "/tmp/.X11-unix")
- }
- }
-
- /*
- PulseAudio server and authentication
- */
-
- if seal.Has(system.EPulse) {
- // check PulseAudio directory presence (e.g. `/run/user/%d/pulse`)
- pd := path.Join(seal.RuntimePath, "pulse")
- ps := path.Join(pd, "native")
- if _, err := os.Stat(pd); err != nil {
- if !errors.Is(err, fs.ErrNotExist) {
- return fmsg.WrapErrorSuffix(err,
- fmt.Sprintf("cannot access PulseAudio directory %q:", pd))
- }
- return fmsg.WrapError(ErrPulseSocket,
- fmt.Sprintf("PulseAudio directory %q not found", pd))
- }
-
- // check PulseAudio socket permission (e.g. `/run/user/%d/pulse/native`)
- if s, err := os.Stat(ps); err != nil {
- if !errors.Is(err, fs.ErrNotExist) {
- return fmsg.WrapErrorSuffix(err,
- fmt.Sprintf("cannot access PulseAudio socket %q:", ps))
- }
- return fmsg.WrapError(ErrPulseSocket,
- fmt.Sprintf("PulseAudio directory %q found but socket does not exist", pd))
- } else {
- if m := s.Mode(); m&0o006 != 0o006 {
- return fmsg.WrapError(ErrPulseMode,
- fmt.Sprintf("unexpected permissions on %q:", ps), m)
- }
- }
-
- // hard link pulse socket into target-executable share
- psi := path.Join(seal.shareLocal, "pulse")
- p := path.Join(seal.innerRuntimeDir, "pulse", "native")
- seal.sys.Link(ps, psi)
- seal.container.Bind(psi, p)
- seal.container.SetEnv[pulseServer] = "unix:" + p
-
- // publish current user's pulse cookie for target user
- if src, err := discoverPulseCookie(os); err != nil {
- // not fatal
- fmsg.Verbose(strings.TrimSpace(err.(*fmsg.BaseError).Message()))
- } else {
- innerDst := fst.Tmp + "/pulse-cookie"
- seal.container.SetEnv[pulseCookie] = innerDst
- payload := new([]byte)
- seal.container.CopyBindRef(innerDst, &payload)
- seal.sys.CopyFile(payload, src, 256, 256)
- }
- }
-
- /*
- D-Bus proxy
- */
-
- if seal.Has(system.EDBus) {
- // ensure dbus session bus defaults
- if bus[0] == nil {
- bus[0] = dbus.NewConfig(seal.appID, true, true)
- }
-
- // downstream socket paths
- sessionPath, systemPath := path.Join(seal.share, "bus"), path.Join(seal.share, "system_bus_socket")
-
- // configure dbus proxy
- if f, err := seal.sys.ProxyDBus(bus[0], bus[1], sessionPath, systemPath); err != nil {
- return err
- } else {
- seal.dbusMsg = f
- }
-
- // share proxy sockets
- sessionInner := path.Join(seal.innerRuntimeDir, "bus")
- seal.container.SetEnv[dbusSessionBusAddress] = "unix:path=" + sessionInner
- seal.container.Bind(sessionPath, sessionInner)
- seal.sys.UpdatePerm(sessionPath, acl.Read, acl.Write)
- if bus[1] != nil {
- systemInner := "/run/dbus/system_bus_socket"
- seal.container.SetEnv[dbusSystemBusAddress] = "unix:path=" + systemInner
- seal.container.Bind(systemPath, systemInner)
- seal.sys.UpdatePerm(systemPath, acl.Read, acl.Write)
- }
- }
-
- /*
- Miscellaneous
- */
-
- // queue overriding tmpfs at the end of seal.container.Filesystem
- for _, dest := range seal.override {
- seal.container.Tmpfs(dest, 8*1024)
- }
-
- // mount fortify in sandbox for init
- seal.container.Bind(os.MustExecutable(), path.Join(fst.Tmp, "sbin/fortify"))
- seal.container.Symlink("fortify", path.Join(fst.Tmp, "sbin/init"))
-
- // append extra perms
- for _, p := range seal.extraPerms {
- if p == nil {
- continue
- }
- if p.ensure {
- seal.sys.Ensure(p.name, 0700)
- }
- seal.sys.UpdatePermType(system.User, p.name, p.perms...)
- }
-
- return nil
-}
-
-// discoverPulseCookie attempts various standard methods to discover the current user's PulseAudio authentication cookie
-func discoverPulseCookie(os sys.State) (string, error) {
- if p, ok := os.LookupEnv(pulseCookie); ok {
- return p, nil
- }
-
- // dotfile $HOME/.pulse-cookie
- if p, ok := os.LookupEnv(home); ok {
- p = path.Join(p, ".pulse-cookie")
- if s, err := os.Stat(p); err != nil {
- if !errors.Is(err, fs.ErrNotExist) {
- return p, fmsg.WrapErrorSuffix(err,
- fmt.Sprintf("cannot access PulseAudio cookie %q:", p))
- }
- // not found, try next method
- } else if !s.IsDir() {
- return p, nil
- }
- }
-
- // $XDG_CONFIG_HOME/pulse/cookie
- if p, ok := os.LookupEnv(xdgConfigHome); ok {
- p = path.Join(p, "pulse", "cookie")
- if s, err := os.Stat(p); err != nil {
- if !errors.Is(err, fs.ErrNotExist) {
- return p, fmsg.WrapErrorSuffix(err,
- fmt.Sprintf("cannot access PulseAudio cookie %q:", p))
- }
- // not found, try next method
- } else if !s.IsDir() {
- return p, nil
- }
- }
-
- return "", fmsg.WrapError(ErrPulseCookie,
- fmt.Sprintf("cannot locate PulseAudio cookie (tried $%s, $%s/pulse/cookie, $%s/.pulse-cookie)",
- pulseCookie, xdgConfigHome, home))
-}