fst: improve config
All checks were successful
Test / Create distribution (push) Successful in 26s
Test / Sandbox (push) Successful in 1m50s
Test / Fortify (push) Successful in 2m46s
Test / Sandbox (race detector) (push) Successful in 2m59s
Test / Fortify (race detector) (push) Successful in 4m23s
Test / Fpkg (push) Successful in 5m25s
Test / Flake checks (push) Successful in 1m1s

The config struct more or less "grew" to what it is today. This change moves things around to make more sense and fixes nonsensical comments describing obsolete behaviour.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
2025-04-13 03:23:28 +09:00
parent c460892cbd
commit 31b7ddd122
21 changed files with 833 additions and 831 deletions

View File

@@ -16,9 +16,9 @@ import (
"git.gensokyo.uk/security/fortify/sandbox/seccomp"
)
// NewContainer initialises [sandbox.Params] via [fst.SandboxConfig].
// NewContainer initialises [sandbox.Params] via [fst.ContainerConfig].
// 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) {
func NewContainer(s *fst.ContainerConfig, os sys.State, uid, gid *int) (*sandbox.Params, map[string]string, error) {
if s == nil {
return nil, nil, syscall.EBADE
}

View File

@@ -13,41 +13,43 @@ var testCasesNixos = []sealTestCase{
{
"nixos chromium direct wayland", new(stubNixOS),
&fst.Config{
ID: "org.chromium.Chromium",
Path: "/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start",
Confinement: fst.ConfinementConfig{
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, 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},
},
Cover: []string{"/var/run/nscd"},
ID: "org.chromium.Chromium",
Path: "/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start",
Enablements: system.EWayland | system.EDBus | system.EPulse,
Container: &fst.ContainerConfig{
Userns: true, Net: true, MapRealUID: 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},
},
SystemBus: &dbus.Config{
Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
Filter: true,
},
SessionBus: &dbus.Config{
Talk: []string{
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
"org.freedesktop.ScreenSaver", "org.freedesktop.secrets",
"org.kde.kwalletd5", "org.kde.kwalletd6",
},
Own: []string{
"org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.chromium.*",
},
Call: map[string]string{}, Broadcast: map[string]string{},
Filter: true,
},
Enablements: system.EWayland | system.EDBus | system.EPulse,
Cover: []string{"/var/run/nscd"},
},
SystemBus: &dbus.Config{
Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
Filter: true,
},
SessionBus: &dbus.Config{
Talk: []string{
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
"org.freedesktop.ScreenSaver", "org.freedesktop.secrets",
"org.kde.kwalletd5", "org.kde.kwalletd6",
},
Own: []string{
"org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.chromium.*",
},
Call: map[string]string{}, Broadcast: map[string]string{},
Filter: true,
},
DirectWayland: true,
Username: "u0_a1",
Data: "/var/lib/persist/module/fortify/0/1",
Identity: 1, Groups: []string{},
},
app.ID{
0x8e, 0x2c, 0x76, 0xb0,

View File

@@ -14,13 +14,7 @@ import (
var testCasesPd = []sealTestCase{
{
"nixos permissive defaults no enablements", new(stubNixOS),
&fst.Config{
Confinement: fst.ConfinementConfig{
AppID: 0,
Username: "chronos",
Outer: "/home/chronos",
},
},
&fst.Config{Username: "chronos", Data: "/home/chronos"},
app.ID{
0x4a, 0x45, 0x0b, 0x65,
0x96, 0xd7, 0xbc, 0x15,
@@ -77,46 +71,44 @@ var testCasesPd = []sealTestCase{
{
"nixos permissive defaults chromium", new(stubNixOS),
&fst.Config{
ID: "org.chromium.Chromium",
Args: []string{"zsh", "-c", "exec chromium "},
Confinement: fst.ConfinementConfig{
AppID: 9,
Groups: []string{"video"},
Username: "chronos",
Outer: "/home/chronos",
SessionBus: &dbus.Config{
Talk: []string{
"org.freedesktop.Notifications",
"org.freedesktop.FileManager1",
"org.freedesktop.ScreenSaver",
"org.freedesktop.secrets",
"org.kde.kwalletd5",
"org.kde.kwalletd6",
"org.gnome.SessionManager",
},
Own: []string{
"org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.chromium.*",
},
Call: map[string]string{
"org.freedesktop.portal.*": "*",
},
Broadcast: map[string]string{
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*",
},
Filter: true,
ID: "org.chromium.Chromium",
Args: []string{"zsh", "-c", "exec chromium "},
Identity: 9,
Groups: []string{"video"},
Username: "chronos",
Data: "/home/chronos",
SessionBus: &dbus.Config{
Talk: []string{
"org.freedesktop.Notifications",
"org.freedesktop.FileManager1",
"org.freedesktop.ScreenSaver",
"org.freedesktop.secrets",
"org.kde.kwalletd5",
"org.kde.kwalletd6",
"org.gnome.SessionManager",
},
SystemBus: &dbus.Config{
Talk: []string{
"org.bluez",
"org.freedesktop.Avahi",
"org.freedesktop.UPower",
},
Filter: true,
Own: []string{
"org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.chromium.*",
},
Enablements: system.EWayland | system.EDBus | system.EPulse,
Call: map[string]string{
"org.freedesktop.portal.*": "*",
},
Broadcast: map[string]string{
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*",
},
Filter: true,
},
SystemBus: &dbus.Config{
Talk: []string{
"org.bluez",
"org.freedesktop.Avahi",
"org.freedesktop.UPower",
},
Filter: true,
},
Enablements: system.EWayland | system.EDBus | system.EPulse,
},
app.ID{
0xeb, 0xf0, 0x83, 0xd1,

View File

@@ -65,7 +65,7 @@ func (seal *outcome) Run(rs *RunState) error {
// accumulate enablements of remaining launchers
for i, s := range states {
if s.Config != nil {
rt |= s.Config.Confinement.Enablements
rt |= s.Config.Enablements
} else {
log.Printf("state entry %d does not contain config", i)
}

View File

@@ -169,16 +169,16 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
}
// allowed aid range 0 to 9999, this is checked again in fsu
if config.Confinement.AppID < 0 || config.Confinement.AppID > 9999 {
if config.Identity < 0 || config.Identity > 9999 {
return fmsg.WrapError(ErrUser,
fmt.Sprintf("aid %d out of range", config.Confinement.AppID))
fmt.Sprintf("identity %d out of range", config.Identity))
}
seal.user = fsuUser{
aid: newInt(config.Confinement.AppID),
data: config.Confinement.Outer,
home: config.Confinement.Inner,
username: config.Confinement.Username,
aid: newInt(config.Identity),
data: config.Data,
home: config.Dir,
username: config.Username,
}
if seal.user.username == "" {
seal.user.username = "chronos"
@@ -199,8 +199,8 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
} else {
seal.user.uid = newInt(u)
}
seal.user.supp = make([]string, len(config.Confinement.Groups))
for i, name := range config.Confinement.Groups {
seal.user.supp = make([]string, len(config.Groups))
for i, name := range config.Groups {
if g, err := sys.LookupGroup(name); err != nil {
return fmsg.WrapError(err,
fmt.Sprintf("unknown group %q", name))
@@ -210,17 +210,17 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
}
// this also falls back to host path if encountering an invalid path
if !path.IsAbs(config.Confinement.Shell) {
config.Confinement.Shell = "/bin/sh"
if !path.IsAbs(config.Shell) {
config.Shell = "/bin/sh"
if s, ok := sys.LookupEnv(shell); ok && path.IsAbs(s) {
config.Confinement.Shell = s
config.Shell = s
}
}
// do not use the value of shell before this point
// permissive defaults
if config.Confinement.Sandbox == nil {
fmsg.Verbose("sandbox configuration not supplied, PROCEED WITH CAUTION")
if config.Container == nil {
fmsg.Verbose("container configuration not supplied, PROCEED WITH CAUTION")
// fsu clears the environment so resolve paths early
if !path.IsAbs(config.Path) {
@@ -231,11 +231,11 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
config.Path = p
}
} else {
config.Path = config.Confinement.Shell
config.Path = config.Shell
}
}
conf := &fst.SandboxConfig{
conf := &fst.ContainerConfig{
Userns: true,
Net: true,
Tty: true,
@@ -268,20 +268,20 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
conf.Cover = append(conf.Cover, nscd)
}
// bind GPU stuff
if config.Confinement.Enablements&(system.EX11|system.EWayland) != 0 {
if config.Enablements&(system.EX11|system.EWayland) != 0 {
conf.Filesystem = append(conf.Filesystem, &fst.FilesystemConfig{Src: "/dev/dri", Device: true})
}
// opportunistically bind kvm
conf.Filesystem = append(conf.Filesystem, &fst.FilesystemConfig{Src: "/dev/kvm", Device: true})
config.Confinement.Sandbox = conf
config.Container = conf
}
var mapuid, mapgid *stringPair[int]
{
var uid, gid int
var err error
seal.container, seal.env, err = common.NewContainer(config.Confinement.Sandbox, sys, &uid, &gid)
seal.container, seal.env, err = common.NewContainer(config.Container, sys, &uid, &gid)
if err != nil {
return fmsg.WrapErrorSuffix(err,
"cannot initialise container configuration:")
@@ -303,12 +303,12 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
}
}
if !config.Confinement.Sandbox.AutoEtc {
if config.Confinement.Sandbox.Etc != "" {
seal.container.Bind(config.Confinement.Sandbox.Etc, "/etc", 0)
if !config.Container.AutoEtc {
if config.Container.Etc != "" {
seal.container.Bind(config.Container.Etc, "/etc", 0)
}
} else {
etcPath := config.Confinement.Sandbox.Etc
etcPath := config.Container.Etc
if etcPath == "" {
etcPath = "/etc"
}
@@ -352,10 +352,10 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
seal.container.Dir = homeDir
seal.env["HOME"] = homeDir
seal.env["USER"] = username
seal.env[shell] = config.Confinement.Shell
seal.env[shell] = config.Shell
seal.container.Place("/etc/passwd",
[]byte(username+":x:"+mapuid.String()+":"+mapgid.String()+":Fortify:"+homeDir+":"+config.Confinement.Shell+"\n"))
[]byte(username+":x:"+mapuid.String()+":"+mapgid.String()+":Fortify:"+homeDir+":"+config.Shell+"\n"))
seal.container.Place("/etc/group",
[]byte("fortify:x:"+mapgid.String()+":\n"))
}
@@ -365,7 +365,7 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
seal.env[term] = t
}
if config.Confinement.Enablements&system.EWayland != 0 {
if config.Enablements&system.EWayland != 0 {
// outer wayland socket (usually `/run/user/%d/wayland-%d`)
var socketPath string
if name, ok := sys.LookupEnv(wl.WaylandDisplay); !ok {
@@ -380,7 +380,7 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
innerPath := path.Join(innerRuntimeDir, wl.FallbackName)
seal.env[wl.WaylandDisplay] = wl.FallbackName
if !config.Confinement.Sandbox.DirectWayland { // set up security-context-v1
if !config.DirectWayland { // set up security-context-v1
appID := config.ID
if appID == "" {
// use instance ID in case app id is not set
@@ -398,7 +398,7 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
}
}
if config.Confinement.Enablements&system.EX11 != 0 {
if config.Enablements&system.EX11 != 0 {
if d, ok := sys.LookupEnv(display); !ok {
return fmsg.WrapError(ErrXDisplay,
"DISPLAY is not set")
@@ -409,7 +409,7 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
}
}
if config.Confinement.Enablements&system.EPulse != 0 {
if config.Enablements&system.EPulse != 0 {
// PulseAudio runtime directory (usually `/run/user/%d/pulse`)
pulseRuntimeDir := path.Join(share.sc.RuntimePath, "pulse")
// PulseAudio socket (usually `/run/user/%d/pulse/native`)
@@ -458,10 +458,10 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
}
}
if config.Confinement.Enablements&system.EDBus != 0 {
if config.Enablements&system.EDBus != 0 {
// ensure dbus session bus defaults
if config.Confinement.SessionBus == nil {
config.Confinement.SessionBus = dbus.NewConfig(config.ID, true, true)
if config.SessionBus == nil {
config.SessionBus = dbus.NewConfig(config.ID, true, true)
}
// downstream socket paths
@@ -470,7 +470,7 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
// configure dbus proxy
if f, err := seal.sys.ProxyDBus(
config.Confinement.SessionBus, config.Confinement.SystemBus,
config.SessionBus, config.SystemBus,
sessionPath, systemPath,
); err != nil {
return err
@@ -483,7 +483,7 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
seal.env[dbusSessionBusAddress] = "unix:path=" + sessionInner
seal.container.Bind(sessionPath, sessionInner, 0)
seal.sys.UpdatePerm(sessionPath, acl.Read, acl.Write)
if config.Confinement.SystemBus != nil {
if config.SystemBus != nil {
systemInner := "/run/dbus/system_bus_socket"
seal.env[dbusSystemBusAddress] = "unix:path=" + systemInner
seal.container.Bind(systemPath, systemInner, 0)
@@ -491,12 +491,12 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
}
}
for _, dest := range config.Confinement.Sandbox.Cover {
for _, dest := range config.Container.Cover {
seal.container.Tmpfs(dest, 1<<13, 0755)
}
// append ExtraPerms last
for _, p := range config.Confinement.ExtraPerms {
for _, p := range config.ExtraPerms {
if p == nil {
continue
}
@@ -530,7 +530,7 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
slices.Sort(seal.container.Env)
fmsg.Verbosef("created application seal for uid %s (%s) groups: %v, argv: %s",
seal.user.uid, seal.user.username, config.Confinement.Groups, seal.container.Args)
seal.user.uid, seal.user.username, config.Groups, seal.container.Args)
return nil
}