diff --git a/fst/config.go b/fst/config.go index 05b588d..27e13f0 100644 --- a/fst/config.go +++ b/fst/config.go @@ -35,6 +35,8 @@ type ConfinementConfig struct { Outer string `json:"home"` // bwrap sandbox confinement configuration Sandbox *SandboxConfig `json:"sandbox"` + // extra acl entries to append + ExtraPerms []*ExtraPermConfig `json:"extra_perms,omitempty"` // reference to a system D-Bus proxy configuration, // nil value disables system bus proxy @@ -78,6 +80,29 @@ type SandboxConfig struct { 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 { // mount point in sandbox, same as src if empty Dst string `json:"dst,omitempty"` diff --git a/internal/app/seal.go b/internal/app/seal.go index 87e62b3..82b7983 100644 --- a/internal/app/seal.go +++ b/internal/app/seal.go @@ -8,6 +8,7 @@ import ( "regexp" "strconv" + "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" @@ -48,6 +49,8 @@ type appSeal struct { et system.Enablements // wayland socket direct access directWayland bool + // extra UpdatePerm ops + extraPerms []*sealedExtraPerm // prevents sharing from happening twice shared bool @@ -59,6 +62,11 @@ type appSeal struct { // protected by upstream mutex } +type sealedExtraPerm struct { + name string + perms acl.Perms +} + // Seal seals the app launch context func (a *app) Seal(config *fst.Config) error { a.lock.Lock() @@ -100,46 +108,66 @@ func (a *app) Seal(config *fst.Config) error { if config.Confinement.AppID < 0 || config.Confinement.AppID > 9999 { return fmsg.WrapError(ErrUser, 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 { - 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 - } + seal.sys.user.uid = u + seal.sys.user.us = strconv.Itoa(u) + } - // 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:") + // resolve supplementary group ids from names + seal.sys.user.supp = make([]string, len(config.Confinement.Groups)) + for i, name := range config.Confinement.Groups { + if g, err := a.os.LookupGroup(name); err != nil { + return fmsg.WrapError(err, + fmt.Sprintf("unknown group %q", name)) } else { - seal.sys.user.uid = u - seal.sys.user.us = strconv.Itoa(u) + seal.sys.user.supp[i] = g.Gid + } + } + + // 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.sys.user.supp = make([]string, len(config.Confinement.Groups)) - for i, name := range config.Confinement.Groups { - if g, err := a.os.LookupGroup(name); err != nil { - return fmsg.WrapError(err, - fmt.Sprintf("unknown group %q", name)) - } else { - seal.sys.user.supp[i] = g.Gid - } + 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) } } diff --git a/internal/app/share.go b/internal/app/share.go index dea55a8..0a18e31 100644 --- a/internal/app/share.go +++ b/internal/app/share.go @@ -292,6 +292,14 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error { 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 } diff --git a/print.go b/print.go index c3126a4..702f35e 100644 --- a/print.go +++ b/print.go @@ -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, "\n") - if !short && config.Confinement.Sandbox != nil && len(config.Confinement.Sandbox.Filesystem) > 0 { - fmt.Fprintf(w, "Filesystem:\n") - for _, f := range config.Confinement.Sandbox.Filesystem { - if f == nil { - continue - } + if !short { + if config.Confinement.Sandbox != nil && len(config.Confinement.Sandbox.Filesystem) > 0 { + fmt.Fprintf(w, "Filesystem\n") + for _, f := range config.Confinement.Sandbox.Filesystem { + if f == nil { + continue + } - expr := new(strings.Builder) - if f.Device { - expr.WriteString(" d") - } else if f.Write { - expr.WriteString(" w") - } else { - expr.WriteString(" ") + expr := new(strings.Builder) + expr.Grow(3 + len(f.Src) + 1 + len(f.Dst)) + + if f.Device { + expr.WriteString(" d") + } else if f.Write { + 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 { - expr.WriteString("*") - } else { - expr.WriteString("+") - } - expr.WriteString(f.Src) - if f.Dst != "" { - expr.WriteString(":" + f.Dst) - } - fmt.Fprintf(w, "%s\n", expr.String()) + fmt.Fprintf(w, "\n") + } + if len(config.Confinement.ExtraPerms) > 0 { + fmt.Fprintf(w, "Extra ACL\n") + for _, p := range config.Confinement.ExtraPerms { + if p == nil { + continue + } + fmt.Fprintf(w, " %s\n", p.String()) + } + fmt.Fprintf(w, "\n") } - fmt.Fprintf(w, "\n") } printDBus := func(c *dbus.Config) {