2024-09-22 00:29:36 +09:00
|
|
|
package app
|
|
|
|
|
|
|
|
import (
|
2025-01-21 12:10:58 +09:00
|
|
|
"bytes"
|
|
|
|
"encoding/gob"
|
2024-09-22 00:29:36 +09:00
|
|
|
"errors"
|
2024-11-16 21:19:45 +09:00
|
|
|
"fmt"
|
2025-01-21 12:10:58 +09:00
|
|
|
"io"
|
2024-10-23 21:46:21 +09:00
|
|
|
"io/fs"
|
2025-02-19 01:36:07 +09:00
|
|
|
"os"
|
2024-10-11 04:18:15 +09:00
|
|
|
"path"
|
2024-11-19 21:01:41 +09:00
|
|
|
"regexp"
|
2024-09-22 00:29:36 +09:00
|
|
|
|
2024-12-28 13:23:27 +09:00
|
|
|
"git.gensokyo.uk/security/fortify/acl"
|
2024-12-20 00:20:02 +09:00
|
|
|
"git.gensokyo.uk/security/fortify/dbus"
|
|
|
|
"git.gensokyo.uk/security/fortify/fst"
|
2025-01-22 01:51:10 +09:00
|
|
|
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
2025-02-14 12:44:55 +09:00
|
|
|
"git.gensokyo.uk/security/fortify/internal"
|
2024-12-20 00:20:02 +09:00
|
|
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
|
|
"git.gensokyo.uk/security/fortify/internal/state"
|
2025-02-19 12:33:51 +09:00
|
|
|
"git.gensokyo.uk/security/fortify/internal/sys"
|
2025-02-17 19:00:43 +09:00
|
|
|
"git.gensokyo.uk/security/fortify/system"
|
2024-09-22 00:29:36 +09:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
ErrConfig = errors.New("no configuration to seal")
|
2024-11-16 21:19:45 +09:00
|
|
|
ErrUser = errors.New("invalid aid")
|
|
|
|
ErrHome = errors.New("invalid home directory")
|
2024-11-19 21:01:41 +09:00
|
|
|
ErrName = errors.New("invalid username")
|
2024-09-22 00:29:36 +09:00
|
|
|
)
|
|
|
|
|
2024-11-19 21:01:41 +09:00
|
|
|
var posixUsername = regexp.MustCompilePOSIX("^[a-z_]([A-Za-z0-9_-]{0,31}|[A-Za-z0-9_-]{0,30}\\$)$")
|
|
|
|
|
2025-02-19 01:04:14 +09:00
|
|
|
// appSeal stores copies of various parts of [fst.Config]
|
2024-10-20 22:54:47 +09:00
|
|
|
type appSeal struct {
|
2025-02-19 01:04:14 +09:00
|
|
|
// string representation of [fst.ID]
|
2024-10-20 22:54:47 +09:00
|
|
|
id string
|
2025-02-07 13:16:54 +09:00
|
|
|
// dump dbus proxy message buffer
|
|
|
|
dbusMsg func()
|
2024-10-20 22:54:47 +09:00
|
|
|
|
2025-02-19 01:04:14 +09:00
|
|
|
// 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
|
2024-10-20 22:54:47 +09:00
|
|
|
command []string
|
2025-02-19 01:36:07 +09:00
|
|
|
|
|
|
|
// state instance initialised during seal; used during process lifecycle events
|
2024-10-20 22:54:47 +09:00
|
|
|
store state.Store
|
2025-02-19 01:36:07 +09:00
|
|
|
// 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
|
2024-10-20 22:54:47 +09:00
|
|
|
|
2025-02-19 01:04:14 +09:00
|
|
|
// process-specific share directory path ([os.TempDir])
|
2024-10-20 22:54:47 +09:00
|
|
|
share string
|
2025-02-19 01:04:14 +09:00
|
|
|
// process-specific share directory path ([fst.Paths] XDG_RUNTIME_DIR)
|
2024-10-20 22:54:47 +09:00
|
|
|
shareLocal string
|
|
|
|
|
2025-02-19 01:04:14 +09:00
|
|
|
// initial [fst.Config] gob stream for state data;
|
|
|
|
// this is prepared ahead of time as config is mutated during seal creation
|
2025-01-21 12:10:58 +09:00
|
|
|
ct io.WriterTo
|
2025-02-19 01:04:14 +09:00
|
|
|
// 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
|
2024-12-06 04:25:33 +09:00
|
|
|
directWayland bool
|
2025-02-19 01:36:07 +09:00
|
|
|
// mount tmpfs over these paths, runs right before extraPerms
|
|
|
|
override []string
|
2025-02-19 01:04:14 +09:00
|
|
|
// extra [acl.Update] ops, appended at the end of [system.I]
|
2024-12-28 13:23:27 +09:00
|
|
|
extraPerms []*sealedExtraPerm
|
2024-10-20 22:54:47 +09:00
|
|
|
|
2025-02-19 01:36:07 +09:00
|
|
|
// 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]
|
|
|
|
|
|
|
|
sys *system.I
|
|
|
|
container *bwrap.Config
|
|
|
|
bwrapSync *os.File
|
|
|
|
|
2024-10-20 22:54:47 +09:00
|
|
|
// prevents sharing from happening twice
|
|
|
|
shared bool
|
|
|
|
|
2025-02-19 00:41:51 +09:00
|
|
|
system.Enablements
|
2025-02-18 23:05:37 +09:00
|
|
|
fst.Paths
|
2024-10-20 22:54:47 +09:00
|
|
|
|
|
|
|
// protected by upstream mutex
|
|
|
|
}
|
|
|
|
|
2025-02-19 01:36:07 +09:00
|
|
|
// appUser stores post-fsu credentials and metadata
|
|
|
|
type appUser struct {
|
|
|
|
// application id
|
|
|
|
aid *stringPair[int]
|
|
|
|
// target uid resolved by fid:aid
|
|
|
|
uid *stringPair[int]
|
|
|
|
|
|
|
|
// supplementary group ids
|
|
|
|
supp []string
|
|
|
|
|
|
|
|
// home directory host path
|
|
|
|
data string
|
|
|
|
// app user home directory
|
|
|
|
home string
|
|
|
|
// passwd database username
|
|
|
|
username string
|
|
|
|
}
|
|
|
|
|
2024-12-28 13:23:27 +09:00
|
|
|
type sealedExtraPerm struct {
|
2024-12-28 14:07:49 +09:00
|
|
|
name string
|
|
|
|
perms acl.Perms
|
|
|
|
ensure bool
|
2024-12-28 13:23:27 +09:00
|
|
|
}
|
|
|
|
|
2025-02-19 12:33:51 +09:00
|
|
|
func (seal *appSeal) finalise(sys sys.State, config *fst.Config, id string) error {
|
2025-01-21 12:10:58 +09:00
|
|
|
// 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
|
|
|
|
|
2025-02-19 12:33:51 +09:00
|
|
|
seal.Paths = sys.Paths()
|
2024-09-22 00:29:36 +09:00
|
|
|
|
|
|
|
// pass through config values
|
2025-02-19 12:33:51 +09:00
|
|
|
seal.id = id
|
2025-02-19 01:04:14 +09:00
|
|
|
seal.appID = config.ID
|
2024-09-22 00:29:36 +09:00
|
|
|
seal.command = config.Command
|
|
|
|
|
2025-02-19 00:25:00 +09:00
|
|
|
{
|
|
|
|
// 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
|
2025-02-19 12:33:51 +09:00
|
|
|
mapuid = sys.Geteuid()
|
2025-02-19 00:25:00 +09:00
|
|
|
}
|
2025-02-19 01:36:07 +09:00
|
|
|
seal.mapuid = newInt(mapuid)
|
|
|
|
seal.innerRuntimeDir = path.Join("/run/user", seal.mapuid.String())
|
2024-11-04 03:15:39 +09:00
|
|
|
}
|
|
|
|
|
2024-11-16 21:19:45 +09:00
|
|
|
// validate uid and set user info
|
|
|
|
if config.Confinement.AppID < 0 || config.Confinement.AppID > 9999 {
|
|
|
|
return fmsg.WrapError(ErrUser,
|
|
|
|
fmt.Sprintf("aid %d out of range", config.Confinement.AppID))
|
2024-12-28 13:23:27 +09:00
|
|
|
}
|
2025-02-19 01:36:07 +09:00
|
|
|
seal.user = appUser{
|
2025-02-19 00:25:00 +09:00
|
|
|
aid: newInt(config.Confinement.AppID),
|
2024-12-28 13:23:27 +09:00
|
|
|
data: config.Confinement.Outer,
|
|
|
|
home: config.Confinement.Inner,
|
|
|
|
username: config.Confinement.Username,
|
|
|
|
}
|
2025-02-19 01:36:07 +09:00
|
|
|
if seal.user.username == "" {
|
|
|
|
seal.user.username = "chronos"
|
|
|
|
} else if !posixUsername.MatchString(seal.user.username) ||
|
|
|
|
len(seal.user.username) >= internal.Sysconf_SC_LOGIN_NAME_MAX() {
|
2024-12-28 13:23:27 +09:00
|
|
|
return fmsg.WrapError(ErrName,
|
2025-02-19 01:36:07 +09:00
|
|
|
fmt.Sprintf("invalid user name %q", seal.user.username))
|
2024-12-28 13:23:27 +09:00
|
|
|
}
|
2025-02-19 01:36:07 +09:00
|
|
|
if seal.user.data == "" || !path.IsAbs(seal.user.data) {
|
2024-12-28 13:23:27 +09:00
|
|
|
return fmsg.WrapError(ErrHome,
|
2025-02-19 01:36:07 +09:00
|
|
|
fmt.Sprintf("invalid home directory %q", seal.user.data))
|
2024-12-28 13:23:27 +09:00
|
|
|
}
|
2025-02-19 01:36:07 +09:00
|
|
|
if seal.user.home == "" {
|
|
|
|
seal.user.home = seal.user.data
|
2024-12-28 13:23:27 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// invoke fsu for full uid
|
2025-02-19 12:33:51 +09:00
|
|
|
if u, err := sys.Uid(seal.user.aid.unwrap()); err != nil {
|
2025-02-18 17:36:58 +09:00
|
|
|
return err
|
2024-11-16 21:19:45 +09:00
|
|
|
} else {
|
2025-02-19 01:36:07 +09:00
|
|
|
seal.user.uid = newInt(u)
|
2024-12-28 13:23:27 +09:00
|
|
|
}
|
2024-11-16 21:19:45 +09:00
|
|
|
|
2024-12-28 13:23:27 +09:00
|
|
|
// resolve supplementary group ids from names
|
2025-02-19 01:36:07 +09:00
|
|
|
seal.user.supp = make([]string, len(config.Confinement.Groups))
|
2024-12-28 13:23:27 +09:00
|
|
|
for i, name := range config.Confinement.Groups {
|
2025-02-19 12:33:51 +09:00
|
|
|
if g, err := sys.LookupGroup(name); err != nil {
|
2024-12-28 13:23:27 +09:00
|
|
|
return fmsg.WrapError(err,
|
|
|
|
fmt.Sprintf("unknown group %q", name))
|
2024-09-22 00:29:36 +09:00
|
|
|
} else {
|
2025-02-19 01:36:07 +09:00
|
|
|
seal.user.supp[i] = g.Gid
|
2024-11-16 21:19:45 +09:00
|
|
|
}
|
2024-12-28 13:23:27 +09:00
|
|
|
}
|
2024-11-16 21:19:45 +09:00
|
|
|
|
2024-12-28 13:23:27 +09:00
|
|
|
// 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)
|
2024-09-22 00:29:36 +09:00
|
|
|
}
|
2024-12-28 14:07:49 +09:00
|
|
|
seal.extraPerms[i].ensure = p.Ensure
|
2024-10-11 04:18:15 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// map sandbox config to bwrap
|
|
|
|
if config.Confinement.Sandbox == nil {
|
2025-02-16 17:26:09 +09:00
|
|
|
fmsg.Verbose("sandbox configuration not supplied, PROCEED WITH CAUTION")
|
2024-10-11 04:18:15 +09:00
|
|
|
|
|
|
|
// permissive defaults
|
2024-12-18 15:50:46 +09:00
|
|
|
conf := &fst.SandboxConfig{
|
2024-10-11 04:18:15 +09:00
|
|
|
UserNS: true,
|
|
|
|
Net: true,
|
2025-01-22 01:51:10 +09:00
|
|
|
Syscall: new(bwrap.SyscallPolicy),
|
2024-10-11 04:18:15 +09:00
|
|
|
NoNewSession: true,
|
2024-11-04 22:18:05 +09:00
|
|
|
AutoEtc: true,
|
2024-10-11 04:18:15 +09:00
|
|
|
}
|
|
|
|
// bind entries in /
|
2025-02-19 12:33:51 +09:00
|
|
|
if d, err := sys.ReadDir("/"); err != nil {
|
2024-10-11 04:18:15 +09:00
|
|
|
return err
|
|
|
|
} else {
|
2024-12-18 15:50:46 +09:00
|
|
|
b := make([]*fst.FilesystemConfig, 0, len(d))
|
2024-10-11 04:18:15 +09:00
|
|
|
for _, ent := range d {
|
2024-10-25 13:31:57 +09:00
|
|
|
p := "/" + ent.Name()
|
|
|
|
switch p {
|
|
|
|
case "/proc":
|
|
|
|
case "/dev":
|
|
|
|
case "/tmp":
|
|
|
|
case "/mnt":
|
|
|
|
case "/etc":
|
2024-11-04 22:18:05 +09:00
|
|
|
|
2024-10-11 04:18:15 +09:00
|
|
|
default:
|
2024-12-18 15:50:46 +09:00
|
|
|
b = append(b, &fst.FilesystemConfig{Src: p, Write: true, Must: true})
|
2024-10-11 04:18:15 +09:00
|
|
|
}
|
|
|
|
}
|
2024-10-12 22:33:04 +09:00
|
|
|
conf.Filesystem = append(conf.Filesystem, b...)
|
2024-10-11 04:18:15 +09:00
|
|
|
}
|
2025-01-15 10:07:51 +09:00
|
|
|
|
2024-10-13 02:43:00 +09:00
|
|
|
// hide nscd from sandbox if present
|
|
|
|
nscd := "/var/run/nscd"
|
2025-02-19 12:33:51 +09:00
|
|
|
if _, err := sys.Stat(nscd); !errors.Is(err, fs.ErrNotExist) {
|
2024-10-16 01:38:59 +09:00
|
|
|
conf.Override = append(conf.Override, nscd)
|
2024-10-13 02:43:00 +09:00
|
|
|
}
|
2024-10-12 22:55:53 +09:00
|
|
|
// bind GPU stuff
|
2024-10-16 14:38:57 +09:00
|
|
|
if config.Confinement.Enablements.Has(system.EX11) || config.Confinement.Enablements.Has(system.EWayland) {
|
2024-12-18 15:50:46 +09:00
|
|
|
conf.Filesystem = append(conf.Filesystem, &fst.FilesystemConfig{Src: "/dev/dri", Device: true})
|
2024-10-12 22:55:53 +09:00
|
|
|
}
|
2024-12-22 12:37:24 +09:00
|
|
|
// opportunistically bind kvm
|
|
|
|
conf.Filesystem = append(conf.Filesystem, &fst.FilesystemConfig{Src: "/dev/kvm", Device: true})
|
2024-10-25 13:31:57 +09:00
|
|
|
|
2024-10-11 04:18:15 +09:00
|
|
|
config.Confinement.Sandbox = conf
|
|
|
|
}
|
2024-12-06 04:25:33 +09:00
|
|
|
seal.directWayland = config.Confinement.Sandbox.DirectWayland
|
2025-02-19 12:33:51 +09:00
|
|
|
if b, err := config.Confinement.Sandbox.Bwrap(sys); err != nil {
|
2024-11-04 22:18:05 +09:00
|
|
|
return err
|
|
|
|
} else {
|
2025-02-19 01:36:07 +09:00
|
|
|
seal.container = b
|
2024-11-04 22:18:05 +09:00
|
|
|
}
|
2025-02-19 01:36:07 +09:00
|
|
|
seal.override = config.Confinement.Sandbox.Override
|
|
|
|
if seal.container.SetEnv == nil {
|
|
|
|
seal.container.SetEnv = make(map[string]string)
|
2024-10-11 04:18:15 +09:00
|
|
|
}
|
|
|
|
|
2024-09-22 00:29:36 +09:00
|
|
|
// 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
|
2024-12-19 21:36:17 +09:00
|
|
|
seal.store = state.NewMulti(seal.RunDirPath)
|
2024-09-22 00:29:36 +09:00
|
|
|
|
2025-02-17 17:54:28 +09:00
|
|
|
// initialise system interface with os uid
|
2025-02-19 01:36:07 +09:00
|
|
|
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
|
2024-09-22 00:29:36 +09:00
|
|
|
|
2025-01-22 01:51:10 +09:00
|
|
|
// pass through enablements
|
2025-02-19 00:41:51 +09:00
|
|
|
seal.Enablements = config.Confinement.Enablements
|
2024-09-22 00:29:36 +09:00
|
|
|
|
|
|
|
// this method calls all share methods in sequence
|
2025-02-19 12:33:51 +09:00
|
|
|
if err := seal.setupShares([2]*dbus.Config{config.Confinement.SessionBus, config.Confinement.SystemBus}, sys); err != nil {
|
2024-09-22 00:29:36 +09:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// verbose log seal information
|
2025-02-16 17:26:09 +09:00
|
|
|
fmsg.Verbosef("created application seal for uid %s (%s) groups: %v, command: %s",
|
2025-02-19 01:36:07 +09:00
|
|
|
seal.user.uid, seal.user.username, config.Confinement.Groups, config.Command)
|
2024-09-22 00:29:36 +09:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|