From db03565614821695efdb283a81a4733e6d321670 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Wed, 15 Jan 2025 09:42:44 +0900 Subject: [PATCH] fst: move sandbox struct to separate file Signed-off-by: Ophestra --- fst/config.go | 131 ----------------------------------------------- fst/sandbox.go | 135 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 131 deletions(-) create mode 100644 fst/sandbox.go diff --git a/fst/config.go b/fst/config.go index 8827b29..7075c6a 100644 --- a/fst/config.go +++ b/fst/config.go @@ -1,11 +1,7 @@ package fst import ( - "errors" - "git.gensokyo.uk/security/fortify/dbus" - "git.gensokyo.uk/security/fortify/helper/bwrap" - "git.gensokyo.uk/security/fortify/internal/linux" "git.gensokyo.uk/security/fortify/internal/system" ) @@ -49,37 +45,6 @@ type ConfinementConfig struct { Enablements system.Enablements `json:"enablements"` } -// SandboxConfig describes resources made available to the sandbox. -type SandboxConfig struct { - // unix hostname within sandbox - Hostname string `json:"hostname,omitempty"` - // allow userns within sandbox - UserNS bool `json:"userns,omitempty"` - // share net namespace - Net bool `json:"net,omitempty"` - // share all devices - Dev bool `json:"dev,omitempty"` - // do not run in new session - NoNewSession bool `json:"no_new_session,omitempty"` - // map target user uid to privileged user uid in the user namespace - MapRealUID bool `json:"map_real_uid"` - // direct access to wayland socket - DirectWayland bool `json:"direct_wayland,omitempty"` - - // final environment variables - Env map[string]string `json:"env"` - // sandbox host filesystem access - Filesystem []*FilesystemConfig `json:"filesystem"` - // symlinks created inside the sandbox - Link [][2]string `json:"symlink"` - // read-only /etc directory - Etc string `json:"etc,omitempty"` - // automatically set up /etc symlinks - AutoEtc bool `json:"auto_etc"` - // paths to override by mounting tmpfs over them - Override []string `json:"override"` -} - type ExtraPermConfig struct { Ensure bool `json:"ensure,omitempty"` Path string `json:"path"` @@ -121,102 +86,6 @@ type FilesystemConfig struct { Must bool `json:"require,omitempty"` } -// Bwrap returns the address of the corresponding bwrap.Config to s. -// Note that remaining tmpfs entries must be queued by the caller prior to launch. -func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) { - if s == nil { - return nil, errors.New("nil sandbox config") - } - - var uid int - if !s.MapRealUID { - uid = 65534 - } else { - uid = os.Geteuid() - } - - conf := (&bwrap.Config{ - Net: s.Net, - UserNS: s.UserNS, - Hostname: s.Hostname, - Clearenv: true, - SetEnv: s.Env, - - /* this is only 4 KiB of memory on a 64-bit system, - permissive defaults on NixOS results in around 100 entries - so this capacity should eliminate copies for most setups */ - Filesystem: make([]bwrap.FSBuilder, 0, 256), - - NewSession: !s.NoNewSession, - DieWithParent: true, - AsInit: true, - - // initialise map - Chmod: make(bwrap.ChmodConfig), - }). - SetUID(uid).SetGID(uid). - Procfs("/proc"). - Tmpfs(Tmp, 4*1024) - - if !s.Dev { - conf.DevTmpfs("/dev").Mqueue("/dev/mqueue") - } else { - conf.Bind("/dev", "/dev", false, true, true) - } - - if !s.AutoEtc { - if s.Etc == "" { - conf.Dir("/etc") - } else { - conf.Bind(s.Etc, "/etc") - } - } - - for _, c := range s.Filesystem { - if c == nil { - continue - } - src := c.Src - dest := c.Dst - if c.Dst == "" { - dest = c.Src - } - conf.Bind(src, dest, !c.Must, c.Write, c.Device) - } - - for _, l := range s.Link { - conf.Symlink(l[0], l[1]) - } - - if s.AutoEtc { - etc := s.Etc - if etc == "" { - etc = "/etc" - } - conf.Bind(etc, Tmp+"/etc") - - // link host /etc contents to prevent passwd/group from being overwritten - if d, err := os.ReadDir(etc); err != nil { - return nil, err - } else { - for _, ent := range d { - name := ent.Name() - switch name { - case "passwd": - case "group": - - case "mtab": - conf.Symlink("/proc/mounts", "/etc/"+name) - default: - conf.Symlink(Tmp+"/etc/"+name, "/etc/"+name) - } - } - } - } - - return conf, nil -} - // Template returns a fully populated instance of Config. func Template() *Config { return &Config{ diff --git a/fst/sandbox.go b/fst/sandbox.go new file mode 100644 index 0000000..32508fd --- /dev/null +++ b/fst/sandbox.go @@ -0,0 +1,135 @@ +package fst + +import ( + "errors" + + "git.gensokyo.uk/security/fortify/helper/bwrap" + "git.gensokyo.uk/security/fortify/internal/linux" +) + +// SandboxConfig describes resources made available to the sandbox. +type SandboxConfig struct { + // unix hostname within sandbox + Hostname string `json:"hostname,omitempty"` + // allow userns within sandbox + UserNS bool `json:"userns,omitempty"` + // share net namespace + Net bool `json:"net,omitempty"` + // share all devices + Dev bool `json:"dev,omitempty"` + // do not run in new session + NoNewSession bool `json:"no_new_session,omitempty"` + // map target user uid to privileged user uid in the user namespace + MapRealUID bool `json:"map_real_uid"` + // direct access to wayland socket + DirectWayland bool `json:"direct_wayland,omitempty"` + + // final environment variables + Env map[string]string `json:"env"` + // sandbox host filesystem access + Filesystem []*FilesystemConfig `json:"filesystem"` + // symlinks created inside the sandbox + Link [][2]string `json:"symlink"` + // read-only /etc directory + Etc string `json:"etc,omitempty"` + // automatically set up /etc symlinks + AutoEtc bool `json:"auto_etc"` + // paths to override by mounting tmpfs over them + Override []string `json:"override"` +} + +// Bwrap returns the address of the corresponding bwrap.Config to s. +// Note that remaining tmpfs entries must be queued by the caller prior to launch. +func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) { + if s == nil { + return nil, errors.New("nil sandbox config") + } + + var uid int + if !s.MapRealUID { + uid = 65534 + } else { + uid = os.Geteuid() + } + + conf := (&bwrap.Config{ + Net: s.Net, + UserNS: s.UserNS, + Hostname: s.Hostname, + Clearenv: true, + SetEnv: s.Env, + + /* this is only 4 KiB of memory on a 64-bit system, + permissive defaults on NixOS results in around 100 entries + so this capacity should eliminate copies for most setups */ + Filesystem: make([]bwrap.FSBuilder, 0, 256), + + NewSession: !s.NoNewSession, + DieWithParent: true, + AsInit: true, + + // initialise map + Chmod: make(bwrap.ChmodConfig), + }). + SetUID(uid).SetGID(uid). + Procfs("/proc"). + Tmpfs(Tmp, 4*1024) + + if !s.Dev { + conf.DevTmpfs("/dev").Mqueue("/dev/mqueue") + } else { + conf.Bind("/dev", "/dev", false, true, true) + } + + if !s.AutoEtc { + if s.Etc == "" { + conf.Dir("/etc") + } else { + conf.Bind(s.Etc, "/etc") + } + } + + for _, c := range s.Filesystem { + if c == nil { + continue + } + src := c.Src + dest := c.Dst + if c.Dst == "" { + dest = c.Src + } + conf.Bind(src, dest, !c.Must, c.Write, c.Device) + } + + for _, l := range s.Link { + conf.Symlink(l[0], l[1]) + } + + if s.AutoEtc { + etc := s.Etc + if etc == "" { + etc = "/etc" + } + conf.Bind(etc, Tmp+"/etc") + + // link host /etc contents to prevent passwd/group from being overwritten + if d, err := os.ReadDir(etc); err != nil { + return nil, err + } else { + for _, ent := range d { + name := ent.Name() + switch name { + case "passwd": + case "group": + + case "mtab": + conf.Symlink("/proc/mounts", "/etc/"+name) + default: + conf.Symlink(Tmp+"/etc/"+name, "/etc/"+name) + } + } + } + } + + return conf, nil +}