aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--fst/path.go11
-rw-r--r--fst/path_test.go85
-rw-r--r--fst/sandbox.go93
-rw-r--r--internal/app/app_pd_test.go74
-rw-r--r--internal/app/app_stub_test.go9
-rw-r--r--internal/app/app_test.go13
-rw-r--r--internal/app/seal.go19
-rw-r--r--internal/linux/interface.go5
-rw-r--r--internal/linux/std.go4
9 files changed, 212 insertions, 101 deletions
diff --git a/fst/path.go b/fst/path.go
new file mode 100644
index 00000000..2bdab87c
--- /dev/null
+++ b/fst/path.go
@@ -0,0 +1,11 @@
+package fst
+
+import (
+ "path/filepath"
+ "strings"
+)
+
+func deepContainsH(basepath, targpath string) (bool, error) {
+ rel, err := filepath.Rel(basepath, targpath)
+ return err == nil && rel != ".." && !strings.HasPrefix(rel, string([]byte{'.', '.', filepath.Separator})), err
+}
diff --git a/fst/path_test.go b/fst/path_test.go
new file mode 100644
index 00000000..35895901
--- /dev/null
+++ b/fst/path_test.go
@@ -0,0 +1,85 @@
+package fst
+
+import (
+ "testing"
+)
+
+func TestDeepContainsH(t *testing.T) {
+ testCases := []struct {
+ name string
+ basepath string
+ targpath string
+ want bool
+ wantErr bool
+ }{
+ {
+ name: "empty",
+ want: true,
+ },
+ {
+ name: "equal abs",
+ basepath: "/run",
+ targpath: "/run",
+ want: true,
+ },
+ {
+ name: "equal rel",
+ basepath: "./run",
+ targpath: "run",
+ want: true,
+ },
+ {
+ name: "contains abs",
+ basepath: "/run",
+ targpath: "/run/dbus",
+ want: true,
+ },
+ {
+ name: "inverse contains abs",
+ basepath: "/run/dbus",
+ targpath: "/run",
+ want: false,
+ },
+ {
+ name: "contains rel",
+ basepath: "../run",
+ targpath: "../run/dbus",
+ want: true,
+ },
+ {
+ name: "inverse contains rel",
+ basepath: "../run/dbus",
+ targpath: "../run",
+ want: false,
+ },
+ {
+ name: "weird abs",
+ basepath: "/run/dbus",
+ targpath: "/run/dbus/../current-system",
+ want: false,
+ },
+ {
+ name: "weird rel",
+ basepath: "../run/dbus",
+ targpath: "../run/dbus/../current-system",
+ want: false,
+ },
+
+ {
+ name: "invalid mix",
+ basepath: "/run",
+ targpath: "./run",
+ wantErr: true,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ if got, err := deepContainsH(tc.basepath, tc.targpath); (err != nil) != tc.wantErr {
+ t.Errorf("deepContainsH() error = %v, wantErr %v", err, tc.wantErr)
+ } else if got != tc.want {
+ t.Errorf("deepContainsH() = %v, want %v", got, tc.want)
+ }
+ })
+ }
+}
diff --git a/fst/sandbox.go b/fst/sandbox.go
index 32508fde..8def9086 100644
--- a/fst/sandbox.go
+++ b/fst/sandbox.go
@@ -2,8 +2,13 @@ package fst
import (
"errors"
+ "fmt"
+ "io/fs"
+ "path"
+ "git.gensokyo.uk/security/fortify/dbus"
"git.gensokyo.uk/security/fortify/helper/bwrap"
+ "git.gensokyo.uk/security/fortify/internal/fmsg"
"git.gensokyo.uk/security/fortify/internal/linux"
)
@@ -68,7 +73,8 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
DieWithParent: true,
AsInit: true,
- // initialise map
+ // initialise unconditionally as Once cannot be justified
+ // for saving such a miniscule amount of memory
Chmod: make(bwrap.ChmodConfig),
}).
SetUID(uid).SetGID(uid).
@@ -89,16 +95,85 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
}
}
+ // retrieve paths and hide them if they're made available in the sandbox
+ var hidePaths []string
+ sc := os.Paths()
+ hidePaths = append(hidePaths, sc.RuntimePath, sc.SharePath)
+ _, systemBusAddr := dbus.Address()
+ if entries, err := dbus.Parse([]byte(systemBusAddr)); err != nil {
+ return nil, err
+ } else {
+ // there is usually only one, do not preallocate
+ for _, entry := range entries {
+ if entry.Method != "unix" {
+ continue
+ }
+ for _, pair := range entry.Values {
+ if pair[0] == "path" {
+ if path.IsAbs(pair[1]) {
+ // get parent dir of socket
+ dir := path.Dir(pair[1])
+ if dir == "." || dir == "/" {
+ fmsg.VPrintf("dbus socket %q is in an unusual location", pair[1])
+ }
+ hidePaths = append(hidePaths, dir)
+ } else {
+ fmsg.VPrintf("dbus socket %q is not absolute", pair[1])
+ }
+ }
+ }
+ }
+ }
+ hidePathMatch := make([]bool, len(hidePaths))
+ for i := range hidePaths {
+ if err := evalSymlinks(os, &hidePaths[i]); err != nil {
+ return nil, err
+ }
+ }
+
for _, c := range s.Filesystem {
if c == nil {
continue
}
- src := c.Src
+
+ if !path.IsAbs(c.Src) {
+ return nil, fmt.Errorf("src path %q is not absolute", c.Src)
+ }
+
dest := c.Dst
if c.Dst == "" {
dest = c.Src
+ } else if !path.IsAbs(dest) {
+ return nil, fmt.Errorf("dst path %q is not absolute", dest)
+ }
+
+ srcH := c.Src
+ if err := evalSymlinks(os, &srcH); err != nil {
+ return nil, err
+ }
+
+ for i := range hidePaths {
+ // skip matched entries
+ if hidePathMatch[i] {
+ continue
+ }
+
+ if ok, err := deepContainsH(srcH, hidePaths[i]); err != nil {
+ return nil, err
+ } else if ok {
+ hidePathMatch[i] = true
+ fmsg.VPrintf("hiding paths from %q", c.Src)
+ }
+ }
+
+ conf.Bind(c.Src, dest, !c.Must, c.Write, c.Device)
+ }
+
+ // hide marked paths before setting up shares
+ for i, ok := range hidePathMatch {
+ if ok {
+ conf.Tmpfs(hidePaths[i], 8192)
}
- conf.Bind(src, dest, !c.Must, c.Write, c.Device)
}
for _, l := range s.Link {
@@ -133,3 +208,15 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
return conf, nil
}
+
+func evalSymlinks(os linux.System, v *string) error {
+ if p, err := os.EvalSymlinks(*v); err != nil {
+ if !errors.Is(err, fs.ErrNotExist) {
+ return err
+ }
+ fmsg.VPrintf("path %q does not yet exist", *v)
+ } else {
+ *v = p
+ }
+ return nil
+}
diff --git a/internal/app/app_pd_test.go b/internal/app/app_pd_test.go
index f025b44d..968a11ab 100644
--- a/internal/app/app_pd_test.go
+++ b/internal/app/app_pd_test.go
@@ -62,45 +62,14 @@ var testCasesPd = []sealTestCase{
Bind("/lib64", "/lib64", false, true).
Bind("/nix", "/nix", false, true).
Bind("/root", "/root", false, true).
+ Bind("/run", "/run", false, true).
Bind("/srv", "/srv", false, true).
Bind("/sys", "/sys", false, true).
Bind("/usr", "/usr", false, true).
Bind("/var", "/var", false, true).
- Bind("/run/agetty.reload", "/run/agetty.reload", false, true).
- Bind("/run/binfmt", "/run/binfmt", false, true).
- Bind("/run/booted-system", "/run/booted-system", false, true).
- Bind("/run/credentials", "/run/credentials", false, true).
- Bind("/run/cryptsetup", "/run/cryptsetup", false, true).
- Bind("/run/current-system", "/run/current-system", false, true).
- Bind("/run/host", "/run/host", false, true).
- Bind("/run/keys", "/run/keys", false, true).
- Bind("/run/libvirt", "/run/libvirt", false, true).
- Bind("/run/libvirtd.pid", "/run/libvirtd.pid", false, true).
- Bind("/run/lock", "/run/lock", false, true).
- Bind("/run/log", "/run/log", false, true).
- Bind("/run/lvm", "/run/lvm", false, true).
- Bind("/run/mount", "/run/mount", false, true).
- Bind("/run/NetworkManager", "/run/NetworkManager", false, true).
- Bind("/run/nginx", "/run/nginx", false, true).
- Bind("/run/nixos", "/run/nixos", false, true).
- Bind("/run/nscd", "/run/nscd", false, true).
- Bind("/run/opengl-driver", "/run/opengl-driver", false, true).
- Bind("/run/pppd", "/run/pppd", false, true).
- Bind("/run/resolvconf", "/run/resolvconf", false, true).
- Bind("/run/sddm", "/run/sddm", false, true).
- Bind("/run/store", "/run/store", false, true).
- Bind("/run/syncoid", "/run/syncoid", false, true).
- Bind("/run/system", "/run/system", false, true).
- Bind("/run/systemd", "/run/systemd", false, true).
- Bind("/run/tmpfiles.d", "/run/tmpfiles.d", false, true).
- Bind("/run/udev", "/run/udev", false, true).
- Bind("/run/udisks2", "/run/udisks2", false, true).
- Bind("/run/utmp", "/run/utmp", false, true).
- Bind("/run/virtlogd.pid", "/run/virtlogd.pid", false, true).
- Bind("/run/wrappers", "/run/wrappers", false, true).
- Bind("/run/zed.pid", "/run/zed.pid", false, true).
- Bind("/run/zed.state", "/run/zed.state", false, true).
Bind("/dev/kvm", "/dev/kvm", true, true, true).
+ Tmpfs("/run/user/1971", 8192).
+ Tmpfs("/run/dbus", 8192).
Bind("/etc", fst.Tmp+"/etc").
Symlink(fst.Tmp+"/etc/alsa", "/etc/alsa").
Symlink(fst.Tmp+"/etc/bashrc", "/etc/bashrc").
@@ -317,46 +286,15 @@ var testCasesPd = []sealTestCase{
Bind("/lib64", "/lib64", false, true).
Bind("/nix", "/nix", false, true).
Bind("/root", "/root", false, true).
+ Bind("/run", "/run", false, true).
Bind("/srv", "/srv", false, true).
Bind("/sys", "/sys", false, true).
Bind("/usr", "/usr", false, true).
Bind("/var", "/var", false, true).
- Bind("/run/agetty.reload", "/run/agetty.reload", false, true).
- Bind("/run/binfmt", "/run/binfmt", false, true).
- Bind("/run/booted-system", "/run/booted-system", false, true).
- Bind("/run/credentials", "/run/credentials", false, true).
- Bind("/run/cryptsetup", "/run/cryptsetup", false, true).
- Bind("/run/current-system", "/run/current-system", false, true).
- Bind("/run/host", "/run/host", false, true).
- Bind("/run/keys", "/run/keys", false, true).
- Bind("/run/libvirt", "/run/libvirt", false, true).
- Bind("/run/libvirtd.pid", "/run/libvirtd.pid", false, true).
- Bind("/run/lock", "/run/lock", false, true).
- Bind("/run/log", "/run/log", false, true).
- Bind("/run/lvm", "/run/lvm", false, true).
- Bind("/run/mount", "/run/mount", false, true).
- Bind("/run/NetworkManager", "/run/NetworkManager", false, true).
- Bind("/run/nginx", "/run/nginx", false, true).
- Bind("/run/nixos", "/run/nixos", false, true).
- Bind("/run/nscd", "/run/nscd", false, true).
- Bind("/run/opengl-driver", "/run/opengl-driver", false, true).
- Bind("/run/pppd", "/run/pppd", false, true).
- Bind("/run/resolvconf", "/run/resolvconf", false, true).
- Bind("/run/sddm", "/run/sddm", false, true).
- Bind("/run/store", "/run/store", false, true).
- Bind("/run/syncoid", "/run/syncoid", false, true).
- Bind("/run/system", "/run/system", false, true).
- Bind("/run/systemd", "/run/systemd", false, true).
- Bind("/run/tmpfiles.d", "/run/tmpfiles.d", false, true).
- Bind("/run/udev", "/run/udev", false, true).
- Bind("/run/udisks2", "/run/udisks2", false, true).
- Bind("/run/utmp", "/run/utmp", false, true).
- Bind("/run/virtlogd.pid", "/run/virtlogd.pid", false, true).
- Bind("/run/wrappers", "/run/wrappers", false, true).
- Bind("/run/zed.pid", "/run/zed.pid", false, true).
- Bind("/run/zed.state", "/run/zed.state", false, true).
Bind("/dev/dri", "/dev/dri", true, true, true).
Bind("/dev/kvm", "/dev/kvm", true, true, true).
+ Tmpfs("/run/user/1971", 8192).
+ Tmpfs("/run/dbus", 8192).
Bind("/etc", fst.Tmp+"/etc").
Symlink(fst.Tmp+"/etc/alsa", "/etc/alsa").
Symlink(fst.Tmp+"/etc/bashrc", "/etc/bashrc").
diff --git a/internal/app/app_stub_test.go b/internal/app/app_stub_test.go
index 9ab09197..4d2e83e9 100644
--- a/internal/app/app_stub_test.go
+++ b/internal/app/app_stub_test.go
@@ -2,7 +2,6 @@ package app_test
import (
"fmt"
- "io"
"io/fs"
"os/user"
"strconv"
@@ -128,12 +127,12 @@ func (s *stubNixOS) Open(name string) (fs.File, error) {
}
}
-func (s *stubNixOS) Exit(code int) {
- panic("called exit on stub with code " + strconv.Itoa(code))
+func (s *stubNixOS) EvalSymlinks(path string) (string, error) {
+ return path, nil
}
-func (s *stubNixOS) Stdout() io.Writer {
- panic("requested stdout")
+func (s *stubNixOS) Exit(code int) {
+ panic("called exit on stub with code " + strconv.Itoa(code))
}
func (s *stubNixOS) Paths() linux.Paths {
diff --git a/internal/app/app_test.go b/internal/app/app_test.go
index a8fd761e..25691ffe 100644
--- a/internal/app/app_test.go
+++ b/internal/app/app_test.go
@@ -1,6 +1,7 @@
package app_test
import (
+ "encoding/json"
"io/fs"
"reflect"
"testing"
@@ -48,14 +49,22 @@ func TestApp(t *testing.T) {
t.Run("compare bwrap", func(t *testing.T) {
if !reflect.DeepEqual(gotBwrap, tc.wantBwrap) {
- t.Errorf("seal: bwrap = %#v, want %#v",
- gotBwrap, tc.wantBwrap)
+ t.Errorf("seal: bwrap =\n%s\n, want\n%s",
+ mustMarshal(gotBwrap), mustMarshal(tc.wantBwrap))
}
})
})
}
}
+func mustMarshal(v any) string {
+ if b, err := json.Marshal(v); err != nil {
+ panic(err.Error())
+ } else {
+ return string(b)
+ }
+}
+
func stubDirEntries(names ...string) (e []fs.DirEntry, err error) {
e = make([]fs.DirEntry, len(names))
for i, name := range names {
diff --git a/internal/app/seal.go b/internal/app/seal.go
index 27663771..a67feb45 100644
--- a/internal/app/seal.go
+++ b/internal/app/seal.go
@@ -194,7 +194,6 @@ func (a *app) Seal(config *fst.Config) error {
switch p {
case "/proc":
case "/dev":
- case "/run":
case "/tmp":
case "/mnt":
case "/etc":
@@ -205,23 +204,7 @@ func (a *app) Seal(config *fst.Config) error {
}
conf.Filesystem = append(conf.Filesystem, b...)
}
- // bind entries in /run
- if d, err := a.os.ReadDir("/run"); err != nil {
- return err
- } else {
- b := make([]*fst.FilesystemConfig, 0, len(d))
- for _, ent := range d {
- name := ent.Name()
- switch name {
- case "user":
- case "dbus":
- default:
- p := "/run/" + name
- b = append(b, &fst.FilesystemConfig{Src: p, Write: true, Must: true})
- }
- }
- conf.Filesystem = append(conf.Filesystem, b...)
- }
+
// hide nscd from sandbox if present
nscd := "/var/run/nscd"
if _, err := a.os.Stat(nscd); !errors.Is(err, fs.ErrNotExist) {
diff --git a/internal/linux/interface.go b/internal/linux/interface.go
index dd0782df..1bb87db9 100644
--- a/internal/linux/interface.go
+++ b/internal/linux/interface.go
@@ -1,7 +1,6 @@
package linux
import (
- "io"
"io/fs"
"os/user"
"path"
@@ -30,10 +29,10 @@ type System interface {
Stat(name string) (fs.FileInfo, error)
// Open provides [os.Open]
Open(name string) (fs.File, error)
+ // EvalSymlinks provides [filepath.EvalSymlinks]
+ EvalSymlinks(path string) (string, error)
// Exit provides [os.Exit].
Exit(code int)
- // Stdout provides [os.Stdout].
- Stdout() io.Writer
// Paths returns a populated [Paths] struct.
Paths() Paths
diff --git a/internal/linux/std.go b/internal/linux/std.go
index a82fff26..7c6b9279 100644
--- a/internal/linux/std.go
+++ b/internal/linux/std.go
@@ -2,11 +2,11 @@ package linux
import (
"errors"
- "io"
"io/fs"
"os"
"os/exec"
"os/user"
+ "path/filepath"
"strconv"
"sync"
"syscall"
@@ -37,8 +37,8 @@ func (s *Std) LookupGroup(name string) (*user.Group, error) { return user.Lookup
func (s *Std) ReadDir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
func (s *Std) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) }
func (s *Std) Open(name string) (fs.File, error) { return os.Open(name) }
+func (s *Std) EvalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) }
func (s *Std) Exit(code int) { fmsg.Exit(code) }
-func (s *Std) Stdout() io.Writer { return os.Stdout }
const xdgRuntimeDir = "XDG_RUNTIME_DIR"