app: extra acl entries from configuration

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2024-12-28 13:23:27 +09:00
parent c70f0612ad
commit 847b667489
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
4 changed files with 134 additions and 59 deletions

View File

@ -35,6 +35,8 @@ type ConfinementConfig struct {
Outer string `json:"home"` Outer string `json:"home"`
// bwrap sandbox confinement configuration // bwrap sandbox confinement configuration
Sandbox *SandboxConfig `json:"sandbox"` Sandbox *SandboxConfig `json:"sandbox"`
// extra acl entries to append
ExtraPerms []*ExtraPermConfig `json:"extra_perms,omitempty"`
// reference to a system D-Bus proxy configuration, // reference to a system D-Bus proxy configuration,
// nil value disables system bus proxy // nil value disables system bus proxy
@ -78,6 +80,29 @@ type SandboxConfig struct {
Override []string `json:"override"` Override []string `json:"override"`
} }
type ExtraPermConfig struct {
Path string `json:"path"`
Read bool `json:"r,omitempty"`
Write bool `json:"w,omitempty"`
Execute bool `json:"x,omitempty"`
}
func (e *ExtraPermConfig) String() string {
buf := make([]byte, 0, 4+len(e.Path))
buf = append(buf, '-', '-', '-', ':')
buf = append(buf, []byte(e.Path)...)
if e.Read {
buf[0] = 'r'
}
if e.Write {
buf[1] = 'w'
}
if e.Execute {
buf[2] = 'x'
}
return string(buf)
}
type FilesystemConfig struct { type FilesystemConfig struct {
// mount point in sandbox, same as src if empty // mount point in sandbox, same as src if empty
Dst string `json:"dst,omitempty"` Dst string `json:"dst,omitempty"`

View File

@ -8,6 +8,7 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"git.gensokyo.uk/security/fortify/acl"
"git.gensokyo.uk/security/fortify/dbus" "git.gensokyo.uk/security/fortify/dbus"
"git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/internal/fmsg" "git.gensokyo.uk/security/fortify/internal/fmsg"
@ -48,6 +49,8 @@ type appSeal struct {
et system.Enablements et system.Enablements
// wayland socket direct access // wayland socket direct access
directWayland bool directWayland bool
// extra UpdatePerm ops
extraPerms []*sealedExtraPerm
// prevents sharing from happening twice // prevents sharing from happening twice
shared bool shared bool
@ -59,6 +62,11 @@ type appSeal struct {
// protected by upstream mutex // protected by upstream mutex
} }
type sealedExtraPerm struct {
name string
perms acl.Perms
}
// Seal seals the app launch context // Seal seals the app launch context
func (a *app) Seal(config *fst.Config) error { func (a *app) Seal(config *fst.Config) error {
a.lock.Lock() a.lock.Lock()
@ -100,46 +108,66 @@ func (a *app) Seal(config *fst.Config) error {
if config.Confinement.AppID < 0 || config.Confinement.AppID > 9999 { if config.Confinement.AppID < 0 || config.Confinement.AppID > 9999 {
return fmsg.WrapError(ErrUser, return fmsg.WrapError(ErrUser,
fmt.Sprintf("aid %d out of range", config.Confinement.AppID)) fmt.Sprintf("aid %d out of range", config.Confinement.AppID))
}
seal.sys.user = appUser{
aid: config.Confinement.AppID,
as: strconv.Itoa(config.Confinement.AppID),
data: config.Confinement.Outer,
home: config.Confinement.Inner,
username: config.Confinement.Username,
}
if seal.sys.user.username == "" {
seal.sys.user.username = "chronos"
} else if !posixUsername.MatchString(seal.sys.user.username) {
return fmsg.WrapError(ErrName,
fmt.Sprintf("invalid user name %q", seal.sys.user.username))
}
if seal.sys.user.data == "" || !path.IsAbs(seal.sys.user.data) {
return fmsg.WrapError(ErrHome,
fmt.Sprintf("invalid home directory %q", seal.sys.user.data))
}
if seal.sys.user.home == "" {
seal.sys.user.home = seal.sys.user.data
}
// invoke fsu for full uid
if u, err := a.os.Uid(seal.sys.user.aid); err != nil {
return fmsg.WrapErrorSuffix(err,
"cannot obtain uid from fsu:")
} else { } else {
seal.sys.user = appUser{ seal.sys.user.uid = u
aid: config.Confinement.AppID, seal.sys.user.us = strconv.Itoa(u)
as: strconv.Itoa(config.Confinement.AppID), }
data: config.Confinement.Outer,
home: config.Confinement.Inner,
username: config.Confinement.Username,
}
if seal.sys.user.username == "" {
seal.sys.user.username = "chronos"
} else if !posixUsername.MatchString(seal.sys.user.username) {
return fmsg.WrapError(ErrName,
fmt.Sprintf("invalid user name %q", seal.sys.user.username))
}
if seal.sys.user.data == "" || !path.IsAbs(seal.sys.user.data) {
return fmsg.WrapError(ErrHome,
fmt.Sprintf("invalid home directory %q", seal.sys.user.data))
}
if seal.sys.user.home == "" {
seal.sys.user.home = seal.sys.user.data
}
// invoke fsu for full uid // resolve supplementary group ids from names
if u, err := a.os.Uid(seal.sys.user.aid); err != nil { seal.sys.user.supp = make([]string, len(config.Confinement.Groups))
return fmsg.WrapErrorSuffix(err, for i, name := range config.Confinement.Groups {
"cannot obtain uid from fsu:") if g, err := a.os.LookupGroup(name); err != nil {
return fmsg.WrapError(err,
fmt.Sprintf("unknown group %q", name))
} else { } else {
seal.sys.user.uid = u seal.sys.user.supp[i] = g.Gid
seal.sys.user.us = strconv.Itoa(u) }
}
// build extra perms
seal.extraPerms = make([]*sealedExtraPerm, len(config.Confinement.ExtraPerms))
for i, p := range config.Confinement.ExtraPerms {
if p == nil {
continue
} }
// resolve supplementary group ids from names seal.extraPerms[i] = new(sealedExtraPerm)
seal.sys.user.supp = make([]string, len(config.Confinement.Groups)) seal.extraPerms[i].name = p.Path
for i, name := range config.Confinement.Groups { seal.extraPerms[i].perms = make(acl.Perms, 0, 3)
if g, err := a.os.LookupGroup(name); err != nil { if p.Read {
return fmsg.WrapError(err, seal.extraPerms[i].perms = append(seal.extraPerms[i].perms, acl.Read)
fmt.Sprintf("unknown group %q", name)) }
} else { if p.Write {
seal.sys.user.supp[i] = g.Gid 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)
} }
} }

View File

@ -292,6 +292,14 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error {
seal.sys.bwrap.Tmpfs(dest, 8*1024) seal.sys.bwrap.Tmpfs(dest, 8*1024)
} }
// append extra perms
for _, p := range seal.extraPerms {
if p == nil {
continue
}
seal.sys.UpdatePermType(system.User, p.name, p.perms...)
}
return nil return nil
} }

View File

@ -90,33 +90,47 @@ func printShow(instance *state.State, config *fst.Config, short bool) {
fmt.Fprintf(w, " Command:\t%s\n", strings.Join(config.Command, " ")) fmt.Fprintf(w, " Command:\t%s\n", strings.Join(config.Command, " "))
fmt.Fprintf(w, "\n") fmt.Fprintf(w, "\n")
if !short && config.Confinement.Sandbox != nil && len(config.Confinement.Sandbox.Filesystem) > 0 { if !short {
fmt.Fprintf(w, "Filesystem:\n") if config.Confinement.Sandbox != nil && len(config.Confinement.Sandbox.Filesystem) > 0 {
for _, f := range config.Confinement.Sandbox.Filesystem { fmt.Fprintf(w, "Filesystem\n")
if f == nil { for _, f := range config.Confinement.Sandbox.Filesystem {
continue if f == nil {
} continue
}
expr := new(strings.Builder) expr := new(strings.Builder)
if f.Device { expr.Grow(3 + len(f.Src) + 1 + len(f.Dst))
expr.WriteString(" d")
} else if f.Write { if f.Device {
expr.WriteString(" w") expr.WriteString(" d")
} else { } else if f.Write {
expr.WriteString(" ") expr.WriteString(" w")
} else {
expr.WriteString(" ")
}
if f.Must {
expr.WriteString("*")
} else {
expr.WriteString("+")
}
expr.WriteString(f.Src)
if f.Dst != "" {
expr.WriteString(":" + f.Dst)
}
fmt.Fprintf(w, "%s\n", expr.String())
} }
if f.Must { fmt.Fprintf(w, "\n")
expr.WriteString("*") }
} else { if len(config.Confinement.ExtraPerms) > 0 {
expr.WriteString("+") fmt.Fprintf(w, "Extra ACL\n")
} for _, p := range config.Confinement.ExtraPerms {
expr.WriteString(f.Src) if p == nil {
if f.Dst != "" { continue
expr.WriteString(":" + f.Dst) }
} fmt.Fprintf(w, " %s\n", p.String())
fmt.Fprintf(w, "%s\n", expr.String()) }
fmt.Fprintf(w, "\n")
} }
fmt.Fprintf(w, "\n")
} }
printDBus := func(c *dbus.Config) { printDBus := func(c *dbus.Config) {