Compare commits
6 Commits
e2489059c1
...
3e11ce6868
Author | SHA1 | Date | |
---|---|---|---|
3e11ce6868 | |||
562f5ed797 | |||
db03565614 | |||
7d99e45b88 | |||
1651eb06df | |||
ac543a1ce8 |
186
dbus/address.go
Normal file
186
dbus/address.go
Normal file
@ -0,0 +1,186 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type AddrEntry struct {
|
||||
Method string `json:"method"`
|
||||
Values [][2]string `json:"values"`
|
||||
}
|
||||
|
||||
// Parse parses D-Bus address according to
|
||||
// https://dbus.freedesktop.org/doc/dbus-specification.html#addresses
|
||||
func Parse(addr []byte) ([]AddrEntry, error) {
|
||||
// Look for a semicolon
|
||||
address := bytes.Split(bytes.TrimSuffix(addr, []byte{';'}), []byte{';'})
|
||||
|
||||
// Allocate for entries
|
||||
v := make([]AddrEntry, len(address))
|
||||
|
||||
for i, s := range address {
|
||||
var pairs [][]byte
|
||||
|
||||
// Look for the colon :
|
||||
if method, list, ok := bytes.Cut(s, []byte{':'}); !ok {
|
||||
return v, &BadAddressError{ErrNoColon, i, s, -1, nil}
|
||||
} else {
|
||||
pairs = bytes.Split(list, []byte{','})
|
||||
v[i].Method = string(method)
|
||||
v[i].Values = make([][2]string, len(pairs))
|
||||
}
|
||||
|
||||
for j, pair := range pairs {
|
||||
key, value, ok := bytes.Cut(pair, []byte{'='})
|
||||
if !ok {
|
||||
return v, &BadAddressError{ErrBadPairSep, i, s, j, pair}
|
||||
}
|
||||
if len(key) == 0 {
|
||||
return v, &BadAddressError{ErrBadPairKey, i, s, j, pair}
|
||||
}
|
||||
if len(value) == 0 {
|
||||
return v, &BadAddressError{ErrBadPairVal, i, s, j, pair}
|
||||
}
|
||||
v[i].Values[j][0] = string(key)
|
||||
|
||||
if val, errno := unescapeValue(value); errno != errSuccess {
|
||||
return v, &BadAddressError{errno, i, s, j, pair}
|
||||
} else {
|
||||
v[i].Values[j][1] = string(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func unescapeValue(v []byte) (val []byte, errno ParseError) {
|
||||
if l := len(v) - (bytes.Count(v, []byte{'%'}) * 2); l < 0 {
|
||||
errno = ErrBadValLength
|
||||
return
|
||||
} else {
|
||||
val = make([]byte, l)
|
||||
}
|
||||
|
||||
var i, skip int
|
||||
for iu, b := range v {
|
||||
if skip > 0 {
|
||||
skip--
|
||||
continue
|
||||
}
|
||||
|
||||
if ib := bytes.IndexByte([]byte("-_/.\\*"), b); ib != -1 { // - // _/.\*
|
||||
goto opt
|
||||
} else if b >= '0' && b <= '9' { // 0-9
|
||||
goto opt
|
||||
} else if b >= 'A' && b <= 'Z' { // A-Z
|
||||
goto opt
|
||||
} else if b >= 'a' && b <= 'z' { // a-z
|
||||
goto opt
|
||||
}
|
||||
|
||||
if b != '%' {
|
||||
errno = ErrBadValByte
|
||||
break
|
||||
}
|
||||
|
||||
skip += 2
|
||||
if iu+2 >= len(v) {
|
||||
errno = ErrBadValHexLength
|
||||
break
|
||||
}
|
||||
if c, err := hex.Decode(val[i:i+1], v[iu+1:iu+3]); err != nil {
|
||||
if errors.As(err, new(hex.InvalidByteError)) {
|
||||
errno = ErrBadValHexByte
|
||||
break
|
||||
}
|
||||
// unreachable
|
||||
panic(err.Error())
|
||||
} else if c != 1 {
|
||||
// unreachable
|
||||
panic(fmt.Sprintf("invalid decode length %d", c))
|
||||
}
|
||||
i++
|
||||
continue
|
||||
|
||||
opt:
|
||||
val[i] = b
|
||||
i++
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type ParseError uint8
|
||||
|
||||
func (e ParseError) Error() string {
|
||||
switch e {
|
||||
case errSuccess:
|
||||
panic("attempted to return success as error")
|
||||
case ErrNoColon:
|
||||
return "address does not contain a colon"
|
||||
case ErrBadPairSep:
|
||||
return "'=' character not found"
|
||||
case ErrBadPairKey:
|
||||
return "'=' character has no key preceding it"
|
||||
case ErrBadPairVal:
|
||||
return "'=' character has no value following it"
|
||||
case ErrBadValLength:
|
||||
return "unescaped value has impossible length"
|
||||
case ErrBadValByte:
|
||||
return "in D-Bus address, characters other than [-0-9A-Za-z_/.\\*] should have been escaped"
|
||||
case ErrBadValHexLength:
|
||||
return "in D-Bus address, percent character was not followed by two hex digits"
|
||||
case ErrBadValHexByte:
|
||||
return "in D-Bus address, percent character was followed by characters other than hex digits"
|
||||
|
||||
default:
|
||||
return fmt.Sprintf("parse error %d", e)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
errSuccess ParseError = iota
|
||||
ErrNoColon
|
||||
ErrBadPairSep
|
||||
ErrBadPairKey
|
||||
ErrBadPairVal
|
||||
ErrBadValLength
|
||||
ErrBadValByte
|
||||
ErrBadValHexLength
|
||||
ErrBadValHexByte
|
||||
)
|
||||
|
||||
type BadAddressError struct {
|
||||
// error type
|
||||
Type ParseError
|
||||
|
||||
// bad entry position
|
||||
EntryPos int
|
||||
// bad entry value
|
||||
EntryVal []byte
|
||||
|
||||
// bad pair position
|
||||
PairPos int
|
||||
// bad pair value
|
||||
PairVal []byte
|
||||
}
|
||||
|
||||
func (a *BadAddressError) Is(err error) bool {
|
||||
var b *BadAddressError
|
||||
return errors.As(err, &b) && a.Type == b.Type &&
|
||||
a.EntryPos == b.EntryPos && slices.Equal(a.EntryVal, b.EntryVal) &&
|
||||
a.PairPos == b.PairPos && slices.Equal(a.PairVal, b.PairVal)
|
||||
}
|
||||
|
||||
func (a *BadAddressError) Error() string {
|
||||
return a.Type.Error()
|
||||
}
|
||||
|
||||
func (a *BadAddressError) Unwrap() error {
|
||||
return a.Type
|
||||
}
|
55
dbus/address_escape_test.go
Normal file
55
dbus/address_escape_test.go
Normal file
@ -0,0 +1,55 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnescapeValue(t *testing.T) {
|
||||
testCases := []struct {
|
||||
value string
|
||||
want string
|
||||
wantErr ParseError
|
||||
}{
|
||||
// upstream test cases
|
||||
{value: "abcde", want: "abcde"},
|
||||
{value: "", want: ""},
|
||||
{value: "%20%20", want: " "},
|
||||
{value: "%24", want: "$"},
|
||||
{value: "%25", want: "%"},
|
||||
{value: "abc%24", want: "abc$"},
|
||||
{value: "%24abc", want: "$abc"},
|
||||
{value: "abc%24abc", want: "abc$abc"},
|
||||
{value: "/", want: "/"},
|
||||
{value: "-", want: "-"},
|
||||
{value: "_", want: "_"},
|
||||
{value: "A", want: "A"},
|
||||
{value: "I", want: "I"},
|
||||
{value: "Z", want: "Z"},
|
||||
{value: "a", want: "a"},
|
||||
{value: "i", want: "i"},
|
||||
{value: "z", want: "z"},
|
||||
/* Bug: https://bugs.freedesktop.org/show_bug.cgi?id=53499 */
|
||||
{value: "%c3%b6", want: "\xc3\xb6"},
|
||||
|
||||
{value: "%a", wantErr: ErrBadValHexLength},
|
||||
{value: "%q", wantErr: ErrBadValHexLength},
|
||||
{value: "%az", wantErr: ErrBadValHexByte},
|
||||
{value: "%%", wantErr: ErrBadValLength},
|
||||
{value: "%$$", wantErr: ErrBadValHexByte},
|
||||
{value: "abc%a", wantErr: ErrBadValHexLength},
|
||||
{value: "%axyz", wantErr: ErrBadValHexByte},
|
||||
{value: "%", wantErr: ErrBadValLength},
|
||||
{value: "$", wantErr: ErrBadValByte},
|
||||
{value: " ", wantErr: ErrBadValByte},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run("unescape "+tc.value, func(t *testing.T) {
|
||||
if got, errno := unescapeValue([]byte(tc.value)); errno != tc.wantErr {
|
||||
t.Errorf("unescapeValue() errno = %v, wantErr %v", errno, tc.wantErr)
|
||||
} else if tc.wantErr == errSuccess && string(got) != tc.want {
|
||||
t.Errorf("unescapeValue() = %q, want %q", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
119
dbus/address_test.go
Normal file
119
dbus/address_test.go
Normal file
@ -0,0 +1,119 @@
|
||||
package dbus_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"git.gensokyo.uk/security/fortify/dbus"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
addr string
|
||||
want []dbus.AddrEntry
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "simple session unix",
|
||||
addr: "unix:path=/run/user/1971/bus",
|
||||
want: []dbus.AddrEntry{{
|
||||
Method: "unix",
|
||||
Values: [][2]string{{"path", "/run/user/1971/bus"}},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "simple upper escape",
|
||||
addr: "debug:name=Test,cat=cute,escaped=%c3%b6",
|
||||
want: []dbus.AddrEntry{{
|
||||
Method: "debug",
|
||||
Values: [][2]string{
|
||||
{"name", "Test"},
|
||||
{"cat", "cute"},
|
||||
{"escaped", "\xc3\xb6"},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "simple bad escape",
|
||||
addr: "debug:name=%",
|
||||
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadValLength,
|
||||
EntryPos: 0, EntryVal: []byte("debug:name=%"), PairPos: 0, PairVal: []byte("name=%")},
|
||||
},
|
||||
|
||||
// upstream test cases
|
||||
{
|
||||
name: "full address success",
|
||||
addr: "unix:path=/tmp/foo;debug:name=test,sliff=sloff;",
|
||||
want: []dbus.AddrEntry{
|
||||
{Method: "unix", Values: [][2]string{{"path", "/tmp/foo"}}},
|
||||
{Method: "debug", Values: [][2]string{{"name", "test"}, {"sliff", "sloff"}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty address",
|
||||
addr: "",
|
||||
wantErr: &dbus.BadAddressError{Type: dbus.ErrNoColon,
|
||||
EntryVal: []byte{}, PairPos: -1},
|
||||
},
|
||||
{
|
||||
name: "no body",
|
||||
addr: "foo",
|
||||
wantErr: &dbus.BadAddressError{Type: dbus.ErrNoColon,
|
||||
EntryPos: 0, EntryVal: []byte("foo"), PairPos: -1},
|
||||
},
|
||||
{
|
||||
name: "no pair separator",
|
||||
addr: "foo:bar",
|
||||
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairSep,
|
||||
EntryPos: 0, EntryVal: []byte("foo:bar"), PairPos: 0, PairVal: []byte("bar")},
|
||||
},
|
||||
{
|
||||
name: "no pair separator multi pair",
|
||||
addr: "foo:bar,baz",
|
||||
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairSep,
|
||||
EntryPos: 0, EntryVal: []byte("foo:bar,baz"), PairPos: 0, PairVal: []byte("bar")},
|
||||
},
|
||||
{
|
||||
name: "no pair separator single valid pair",
|
||||
addr: "foo:bar=foo,baz",
|
||||
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairSep,
|
||||
EntryPos: 0, EntryVal: []byte("foo:bar=foo,baz"), PairPos: 1, PairVal: []byte("baz")},
|
||||
},
|
||||
{
|
||||
name: "no body single valid address",
|
||||
addr: "foo:bar=foo;baz",
|
||||
wantErr: &dbus.BadAddressError{Type: dbus.ErrNoColon,
|
||||
EntryPos: 1, EntryVal: []byte("baz"), PairPos: -1},
|
||||
},
|
||||
{
|
||||
name: "no key",
|
||||
addr: "foo:=foo",
|
||||
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairKey,
|
||||
EntryPos: 0, EntryVal: []byte("foo:=foo"), PairPos: 0, PairVal: []byte("=foo")},
|
||||
},
|
||||
{
|
||||
name: "no value",
|
||||
addr: "foo:foo=",
|
||||
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairVal,
|
||||
EntryPos: 0, EntryVal: []byte("foo:foo="), PairPos: 0, PairVal: []byte("foo=")},
|
||||
},
|
||||
{
|
||||
name: "no pair separator single valid pair trailing",
|
||||
addr: "foo:foo,bar=baz",
|
||||
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairSep,
|
||||
EntryPos: 0, EntryVal: []byte("foo:foo,bar=baz"), PairPos: 0, PairVal: []byte("foo")},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got, err := dbus.Parse([]byte(tc.addr)); !errors.Is(err, tc.wantErr) {
|
||||
t.Errorf("Parse() error = %v, wantErr %v", err, tc.wantErr)
|
||||
} else if tc.wantErr == nil && !reflect.DeepEqual(got, tc.want) {
|
||||
t.Errorf("Parse() = %#v, want %#v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
func TestConfig_Args(t *testing.T) {
|
||||
for _, tc := range testCases() {
|
||||
for _, tc := range makeTestCases() {
|
||||
if tc.wantErr {
|
||||
// args does not check for nulls
|
||||
continue
|
||||
@ -30,7 +30,7 @@ func TestConfig_Args(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewConfigFromFile(t *testing.T) {
|
||||
for _, tc := range testCases() {
|
||||
for _, tc := range makeTestCases() {
|
||||
name := new(strings.Builder)
|
||||
name.WriteString("parse configuration file for application ")
|
||||
name.WriteString(tc.id)
|
||||
|
@ -145,7 +145,7 @@ var (
|
||||
testCaseOnce sync.Once
|
||||
)
|
||||
|
||||
func testCases() []dbusTestCase {
|
||||
func makeTestCases() []dbusTestCase {
|
||||
testCaseOnce.Do(testCaseGenerate)
|
||||
return testCasesV
|
||||
}
|
||||
|
131
fst/config.go
131
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{
|
||||
|
11
fst/path.go
Normal file
11
fst/path.go
Normal file
@ -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
|
||||
}
|
85
fst/path_test.go
Normal file
85
fst/path_test.go
Normal file
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
222
fst/sandbox.go
Normal file
222
fst/sandbox.go
Normal file
@ -0,0 +1,222 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// 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 unconditionally as Once cannot be justified
|
||||
// for saving such a miniscule amount of memory
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
@ -20,7 +20,7 @@ type bubblewrap struct {
|
||||
name string
|
||||
|
||||
// bwrap pipes
|
||||
p *pipes
|
||||
control *pipes
|
||||
// sync pipe
|
||||
sync *os.File
|
||||
// returns an array of arguments passed directly
|
||||
@ -29,7 +29,7 @@ type bubblewrap struct {
|
||||
|
||||
// pipes received by the child
|
||||
// nil if no pipes are required
|
||||
cp *pipes
|
||||
controlPt *pipes
|
||||
|
||||
lock sync.RWMutex
|
||||
*exec.Cmd
|
||||
@ -39,7 +39,7 @@ func (b *bubblewrap) StartNotify(ready chan error) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
if ready != nil && b.cp == nil {
|
||||
if ready != nil && b.controlPt == nil {
|
||||
panic("attempted to start with status monitoring on a bwrap child initialised without pipes")
|
||||
}
|
||||
|
||||
@ -50,16 +50,16 @@ func (b *bubblewrap) StartNotify(ready chan error) error {
|
||||
}
|
||||
|
||||
// prepare bwrap pipe and args
|
||||
if argsFD, _, err := b.p.prepareCmd(b.Cmd); err != nil {
|
||||
if argsFD, _, err := b.control.prepareCmd(b.Cmd); err != nil {
|
||||
return err
|
||||
} else {
|
||||
b.Cmd.Args = append(b.Cmd.Args, "--args", strconv.Itoa(argsFD), "--", b.name)
|
||||
}
|
||||
|
||||
// prepare child args and pipes if enabled
|
||||
if b.cp != nil {
|
||||
b.cp.ready = ready
|
||||
if argsFD, statFD, err := b.cp.prepareCmd(b.Cmd); err != nil {
|
||||
if b.controlPt != nil {
|
||||
b.controlPt.ready = ready
|
||||
if argsFD, statFD, err := b.controlPt.prepareCmd(b.Cmd); err != nil {
|
||||
return err
|
||||
} else {
|
||||
b.Cmd.Args = append(b.Cmd.Args, b.argF(argsFD, statFD)...)
|
||||
@ -70,7 +70,7 @@ func (b *bubblewrap) StartNotify(ready chan error) error {
|
||||
|
||||
if ready != nil {
|
||||
b.Cmd.Env = append(b.Cmd.Env, FortifyHelper+"=1", FortifyStatus+"=1")
|
||||
} else if b.cp != nil {
|
||||
} else if b.controlPt != nil {
|
||||
b.Cmd.Env = append(b.Cmd.Env, FortifyHelper+"=1", FortifyStatus+"=0")
|
||||
} else {
|
||||
b.Cmd.Env = append(b.Cmd.Env, FortifyHelper+"=1", FortifyStatus+"=-1")
|
||||
@ -85,13 +85,13 @@ func (b *bubblewrap) StartNotify(ready chan error) error {
|
||||
}
|
||||
|
||||
// write bwrap args first
|
||||
if err := b.p.readyWriteArgs(); err != nil {
|
||||
if err := b.control.readyWriteArgs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// write child args if enabled
|
||||
if b.cp != nil {
|
||||
if err := b.cp.readyWriteArgs(); err != nil {
|
||||
if b.controlPt != nil {
|
||||
if err := b.controlPt.readyWriteArgs(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -100,11 +100,11 @@ func (b *bubblewrap) StartNotify(ready chan error) error {
|
||||
}
|
||||
|
||||
func (b *bubblewrap) Close() error {
|
||||
if b.cp == nil {
|
||||
if b.controlPt == nil {
|
||||
panic("attempted to close bwrap child initialised without pipes")
|
||||
}
|
||||
|
||||
return b.cp.closeStatus()
|
||||
return b.controlPt.closeStatus()
|
||||
}
|
||||
|
||||
func (b *bubblewrap) Start() error {
|
||||
@ -136,14 +136,14 @@ func NewBwrap(conf *bwrap.Config, wt io.WriterTo, name string, argF func(argsFD,
|
||||
if args, err := NewCheckedArgs(conf.Args()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
b.p = &pipes{args: args}
|
||||
b.control = &pipes{args: args}
|
||||
}
|
||||
|
||||
b.sync = conf.Sync()
|
||||
b.argF = argF
|
||||
b.name = name
|
||||
if wt != nil {
|
||||
b.cp = &pipes{args: wt}
|
||||
b.controlPt = &pipes{args: wt}
|
||||
}
|
||||
b.Cmd = execCommand(BubblewrapName)
|
||||
|
||||
|
@ -1,23 +0,0 @@
|
||||
package bwrap
|
||||
|
||||
const (
|
||||
Tmpfs = iota
|
||||
Dir
|
||||
Symlink
|
||||
|
||||
OverlaySrc
|
||||
Overlay
|
||||
TmpOverlay
|
||||
ROOverlay
|
||||
)
|
||||
|
||||
var awkwardArgs = [...]string{
|
||||
Tmpfs: "--tmpfs",
|
||||
Dir: "--dir",
|
||||
Symlink: "--symlink",
|
||||
|
||||
OverlaySrc: "--overlay-src",
|
||||
Overlay: "--overlay",
|
||||
TmpOverlay: "--tmp-overlay",
|
||||
ROOverlay: "--ro-overlay",
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
package bwrap
|
||||
|
||||
const (
|
||||
UnshareAll = iota
|
||||
UnshareUser
|
||||
UnshareIPC
|
||||
UnsharePID
|
||||
UnshareNet
|
||||
UnshareUTS
|
||||
UnshareCGroup
|
||||
ShareNet
|
||||
|
||||
UserNS
|
||||
Clearenv
|
||||
|
||||
NewSession
|
||||
DieWithParent
|
||||
AsInit
|
||||
)
|
||||
|
||||
var boolArgs = [...][]string{
|
||||
UnshareAll: {"--unshare-all", "--unshare-user"},
|
||||
UnshareUser: {"--unshare-user"},
|
||||
UnshareIPC: {"--unshare-ipc"},
|
||||
UnsharePID: {"--unshare-pid"},
|
||||
UnshareNet: {"--unshare-net"},
|
||||
UnshareUTS: {"--unshare-uts"},
|
||||
UnshareCGroup: {"--unshare-cgroup"},
|
||||
ShareNet: {"--share-net"},
|
||||
|
||||
UserNS: {"--disable-userns", "--assert-userns-disabled"},
|
||||
Clearenv: {"--clearenv"},
|
||||
|
||||
NewSession: {"--new-session"},
|
||||
DieWithParent: {"--die-with-parent"},
|
||||
AsInit: {"--as-pid-1"},
|
||||
}
|
||||
|
||||
func (c *Config) boolArgs() Builder {
|
||||
b := boolArg{
|
||||
UserNS: !c.UserNS,
|
||||
Clearenv: c.Clearenv,
|
||||
|
||||
NewSession: c.NewSession,
|
||||
DieWithParent: c.DieWithParent,
|
||||
AsInit: c.AsInit,
|
||||
}
|
||||
|
||||
if c.Unshare == nil {
|
||||
b[UnshareAll] = true
|
||||
b[ShareNet] = c.Net
|
||||
} else {
|
||||
b[UnshareUser] = c.Unshare.User
|
||||
b[UnshareIPC] = c.Unshare.IPC
|
||||
b[UnsharePID] = c.Unshare.PID
|
||||
b[UnshareNet] = c.Unshare.Net
|
||||
b[UnshareUTS] = c.Unshare.UTS
|
||||
b[UnshareCGroup] = c.Unshare.CGroup
|
||||
}
|
||||
|
||||
return &b
|
||||
}
|
||||
|
||||
type boolArg [len(boolArgs)]bool
|
||||
|
||||
func (b *boolArg) Len() (l int) {
|
||||
for i, v := range b {
|
||||
if v {
|
||||
l += len(boolArgs[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *boolArg) Append(args *[]string) {
|
||||
for i, v := range b {
|
||||
if v {
|
||||
*args = append(*args, boolArgs[i]...)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package bwrap
|
||||
|
||||
import "strconv"
|
||||
|
||||
const (
|
||||
UID = iota
|
||||
GID
|
||||
Perms
|
||||
Size
|
||||
)
|
||||
|
||||
var intArgs = [...]string{
|
||||
UID: "--uid",
|
||||
GID: "--gid",
|
||||
Perms: "--perms",
|
||||
Size: "--size",
|
||||
}
|
||||
|
||||
func (c *Config) intArgs() Builder {
|
||||
// Arg types:
|
||||
// Perms
|
||||
// are handled by the sequential builder
|
||||
|
||||
return &intArg{
|
||||
UID: c.UID,
|
||||
GID: c.GID,
|
||||
}
|
||||
}
|
||||
|
||||
type intArg [len(intArgs)]*int
|
||||
|
||||
func (n *intArg) Len() (l int) {
|
||||
for _, v := range n {
|
||||
if v != nil {
|
||||
l += 2
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (n *intArg) Append(args *[]string) {
|
||||
for i, v := range n {
|
||||
if v != nil {
|
||||
*args = append(*args, intArgs[i], strconv.Itoa(*v))
|
||||
}
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
package bwrap
|
||||
|
||||
import (
|
||||
"slices"
|
||||
)
|
||||
|
||||
const (
|
||||
SetEnv = iota
|
||||
|
||||
Bind
|
||||
BindTry
|
||||
DevBind
|
||||
DevBindTry
|
||||
ROBind
|
||||
ROBindTry
|
||||
|
||||
Chmod
|
||||
)
|
||||
|
||||
var pairArgs = [...]string{
|
||||
SetEnv: "--setenv",
|
||||
|
||||
Bind: "--bind",
|
||||
BindTry: "--bind-try",
|
||||
DevBind: "--dev-bind",
|
||||
DevBindTry: "--dev-bind-try",
|
||||
ROBind: "--ro-bind",
|
||||
ROBindTry: "--ro-bind-try",
|
||||
|
||||
Chmod: "--chmod",
|
||||
}
|
||||
|
||||
func (c *Config) pairArgs() Builder {
|
||||
var n pairArg
|
||||
n[SetEnv] = make([][2]string, len(c.SetEnv))
|
||||
keys := make([]string, 0, len(c.SetEnv))
|
||||
for k := range c.SetEnv {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
slices.Sort(keys)
|
||||
for i, k := range keys {
|
||||
n[SetEnv][i] = [2]string{k, c.SetEnv[k]}
|
||||
}
|
||||
|
||||
// Arg types:
|
||||
// Bind
|
||||
// BindTry
|
||||
// DevBind
|
||||
// DevBindTry
|
||||
// ROBind
|
||||
// ROBindTry
|
||||
// Chmod
|
||||
// are handled by the sequential builder
|
||||
|
||||
return &n
|
||||
}
|
||||
|
||||
type pairArg [len(pairArgs)][][2]string
|
||||
|
||||
func (p *pairArg) Len() (l int) {
|
||||
for _, v := range p {
|
||||
l += len(v) * 3
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *pairArg) Append(args *[]string) {
|
||||
for i, arg := range p {
|
||||
for _, v := range arg {
|
||||
*args = append(*args, pairArgs[i], v[0], v[1])
|
||||
}
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
package bwrap
|
||||
|
||||
const (
|
||||
Hostname = iota
|
||||
Chdir
|
||||
UnsetEnv
|
||||
LockFile
|
||||
|
||||
RemountRO
|
||||
Procfs
|
||||
DevTmpfs
|
||||
Mqueue
|
||||
)
|
||||
|
||||
var stringArgs = [...]string{
|
||||
Hostname: "--hostname",
|
||||
Chdir: "--chdir",
|
||||
UnsetEnv: "--unsetenv",
|
||||
LockFile: "--lock-file",
|
||||
|
||||
RemountRO: "--remount-ro",
|
||||
Procfs: "--proc",
|
||||
DevTmpfs: "--dev",
|
||||
Mqueue: "--mqueue",
|
||||
}
|
||||
|
||||
func (c *Config) stringArgs() Builder {
|
||||
n := stringArg{
|
||||
UnsetEnv: c.UnsetEnv,
|
||||
LockFile: c.LockFile,
|
||||
}
|
||||
|
||||
if c.Hostname != "" {
|
||||
n[Hostname] = []string{c.Hostname}
|
||||
}
|
||||
if c.Chdir != "" {
|
||||
n[Chdir] = []string{c.Chdir}
|
||||
}
|
||||
|
||||
// Arg types:
|
||||
// RemountRO
|
||||
// Procfs
|
||||
// DevTmpfs
|
||||
// Mqueue
|
||||
// are handled by the sequential builder
|
||||
|
||||
return &n
|
||||
}
|
||||
|
||||
type stringArg [len(stringArgs)][]string
|
||||
|
||||
func (s *stringArg) Len() (l int) {
|
||||
for _, arg := range s {
|
||||
l += len(arg) * 2
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *stringArg) Append(args *[]string) {
|
||||
for i, arg := range s {
|
||||
for _, v := range arg {
|
||||
*args = append(*args, stringArgs[i], v)
|
||||
}
|
||||
}
|
||||
}
|
@ -39,46 +39,60 @@ func (c *Config) Bind(src, dest string, opts ...bool) *Config {
|
||||
|
||||
if dev {
|
||||
if try {
|
||||
c.Filesystem = append(c.Filesystem, &pairF{pairArgs[DevBindTry], src, dest})
|
||||
c.Filesystem = append(c.Filesystem, &pairF{DevBindTry.Unwrap(), src, dest})
|
||||
} else {
|
||||
c.Filesystem = append(c.Filesystem, &pairF{pairArgs[DevBind], src, dest})
|
||||
c.Filesystem = append(c.Filesystem, &pairF{DevBind.Unwrap(), src, dest})
|
||||
}
|
||||
return c
|
||||
} else if write {
|
||||
if try {
|
||||
c.Filesystem = append(c.Filesystem, &pairF{pairArgs[BindTry], src, dest})
|
||||
c.Filesystem = append(c.Filesystem, &pairF{BindTry.Unwrap(), src, dest})
|
||||
} else {
|
||||
c.Filesystem = append(c.Filesystem, &pairF{pairArgs[Bind], src, dest})
|
||||
c.Filesystem = append(c.Filesystem, &pairF{Bind.Unwrap(), src, dest})
|
||||
}
|
||||
return c
|
||||
} else {
|
||||
if try {
|
||||
c.Filesystem = append(c.Filesystem, &pairF{pairArgs[ROBindTry], src, dest})
|
||||
c.Filesystem = append(c.Filesystem, &pairF{ROBindTry.Unwrap(), src, dest})
|
||||
} else {
|
||||
c.Filesystem = append(c.Filesystem, &pairF{pairArgs[ROBind], src, dest})
|
||||
c.Filesystem = append(c.Filesystem, &pairF{ROBind.Unwrap(), src, dest})
|
||||
}
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
// Dir create dir in sandbox
|
||||
// (--dir DEST)
|
||||
func (c *Config) Dir(dest string) *Config {
|
||||
c.Filesystem = append(c.Filesystem, &stringF{Dir.Unwrap(), dest})
|
||||
return c
|
||||
}
|
||||
|
||||
// RemountRO remount path as readonly; does not recursively remount
|
||||
// (--remount-ro DEST)
|
||||
func (c *Config) RemountRO(dest string) *Config {
|
||||
c.Filesystem = append(c.Filesystem, &stringF{stringArgs[RemountRO], dest})
|
||||
c.Filesystem = append(c.Filesystem, &stringF{RemountRO.Unwrap(), dest})
|
||||
return c
|
||||
}
|
||||
|
||||
// Procfs mount new procfs in sandbox
|
||||
// (--proc DEST)
|
||||
func (c *Config) Procfs(dest string) *Config {
|
||||
c.Filesystem = append(c.Filesystem, &stringF{stringArgs[Procfs], dest})
|
||||
c.Filesystem = append(c.Filesystem, &stringF{Procfs.Unwrap(), dest})
|
||||
return c
|
||||
}
|
||||
|
||||
// DevTmpfs mount new dev in sandbox
|
||||
// (--dev DEST)
|
||||
func (c *Config) DevTmpfs(dest string) *Config {
|
||||
c.Filesystem = append(c.Filesystem, &stringF{stringArgs[DevTmpfs], dest})
|
||||
c.Filesystem = append(c.Filesystem, &stringF{DevTmpfs.Unwrap(), dest})
|
||||
return c
|
||||
}
|
||||
|
||||
// Mqueue mount new mqueue in sandbox
|
||||
// (--mqueue DEST)
|
||||
func (c *Config) Mqueue(dest string) *Config {
|
||||
c.Filesystem = append(c.Filesystem, &stringF{Mqueue.Unwrap(), dest})
|
||||
return c
|
||||
}
|
||||
|
||||
@ -121,20 +135,6 @@ func (c *Config) Persist(dest, rwsrc, workdir string, src ...string) *Config {
|
||||
return c
|
||||
}
|
||||
|
||||
// Mqueue mount new mqueue in sandbox
|
||||
// (--mqueue DEST)
|
||||
func (c *Config) Mqueue(dest string) *Config {
|
||||
c.Filesystem = append(c.Filesystem, &stringF{stringArgs[Mqueue], dest})
|
||||
return c
|
||||
}
|
||||
|
||||
// Dir create dir in sandbox
|
||||
// (--dir DEST)
|
||||
func (c *Config) Dir(dest string) *Config {
|
||||
c.Filesystem = append(c.Filesystem, &stringF{awkwardArgs[Dir], dest})
|
||||
return c
|
||||
}
|
||||
|
||||
// Symlink create symlink within sandbox
|
||||
// (--symlink SRC DEST)
|
||||
func (c *Config) Symlink(src, dest string, perm ...os.FileMode) *Config {
|
@ -1,16 +1,9 @@
|
||||
package bwrap
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gob.Register(new(PermConfig[SymlinkConfig]))
|
||||
gob.Register(new(PermConfig[*TmpfsConfig]))
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
// unshare every namespace we support by default if nil
|
||||
// (--unshare-all)
|
||||
@ -123,153 +116,3 @@ type UnshareConfig struct {
|
||||
// create new cgroup namespace
|
||||
CGroup bool `json:"cgroup"`
|
||||
}
|
||||
|
||||
type PermConfig[T FSBuilder] struct {
|
||||
// set permissions of next argument
|
||||
// (--perms OCTAL)
|
||||
Mode *os.FileMode `json:"mode,omitempty"`
|
||||
// path to get the new permission
|
||||
// (--bind-data, --file, etc.)
|
||||
Inner T `json:"path"`
|
||||
}
|
||||
|
||||
func (p *PermConfig[T]) Path() string {
|
||||
return p.Inner.Path()
|
||||
}
|
||||
|
||||
func (p *PermConfig[T]) Len() int {
|
||||
if p.Mode != nil {
|
||||
return p.Inner.Len() + 2
|
||||
} else {
|
||||
return p.Inner.Len()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PermConfig[T]) Append(args *[]string) {
|
||||
if p.Mode != nil {
|
||||
*args = append(*args, intArgs[Perms], strconv.FormatInt(int64(*p.Mode), 8))
|
||||
}
|
||||
p.Inner.Append(args)
|
||||
}
|
||||
|
||||
type TmpfsConfig struct {
|
||||
// set size of tmpfs
|
||||
// (--size BYTES)
|
||||
Size int `json:"size,omitempty"`
|
||||
// mount point of new tmpfs
|
||||
// (--tmpfs DEST)
|
||||
Dir string `json:"dir"`
|
||||
}
|
||||
|
||||
func (t *TmpfsConfig) Path() string {
|
||||
return t.Dir
|
||||
}
|
||||
|
||||
func (t *TmpfsConfig) Len() int {
|
||||
if t.Size > 0 {
|
||||
return 4
|
||||
} else {
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TmpfsConfig) Append(args *[]string) {
|
||||
if t.Size > 0 {
|
||||
*args = append(*args, intArgs[Size], strconv.Itoa(t.Size))
|
||||
}
|
||||
*args = append(*args, awkwardArgs[Tmpfs], t.Dir)
|
||||
}
|
||||
|
||||
type OverlayConfig struct {
|
||||
/*
|
||||
read files from SRC in the following overlay
|
||||
(--overlay-src SRC)
|
||||
*/
|
||||
Src []string `json:"src,omitempty"`
|
||||
|
||||
/*
|
||||
mount overlayfs on DEST, with RWSRC as the host path for writes and
|
||||
WORKDIR an empty directory on the same filesystem as RWSRC
|
||||
(--overlay RWSRC WORKDIR DEST)
|
||||
|
||||
if nil, mount overlayfs on DEST, with writes going to an invisible tmpfs
|
||||
(--tmp-overlay DEST)
|
||||
|
||||
if either strings are empty, mount overlayfs read-only on DEST
|
||||
(--ro-overlay DEST)
|
||||
*/
|
||||
Persist *[2]string `json:"persist,omitempty"`
|
||||
|
||||
/*
|
||||
--overlay RWSRC WORKDIR DEST
|
||||
|
||||
--tmp-overlay DEST
|
||||
|
||||
--ro-overlay DEST
|
||||
*/
|
||||
Dest string `json:"dest"`
|
||||
}
|
||||
|
||||
func (o *OverlayConfig) Path() string {
|
||||
return o.Dest
|
||||
}
|
||||
|
||||
func (o *OverlayConfig) Len() int {
|
||||
// (--tmp-overlay DEST) or (--ro-overlay DEST)
|
||||
p := 2
|
||||
// (--overlay RWSRC WORKDIR DEST)
|
||||
if o.Persist != nil && o.Persist[0] != "" && o.Persist[1] != "" {
|
||||
p = 4
|
||||
}
|
||||
|
||||
return p + len(o.Src)*2
|
||||
}
|
||||
|
||||
func (o *OverlayConfig) Append(args *[]string) {
|
||||
// --overlay-src SRC
|
||||
for _, src := range o.Src {
|
||||
*args = append(*args, awkwardArgs[OverlaySrc], src)
|
||||
}
|
||||
|
||||
if o.Persist != nil {
|
||||
if o.Persist[0] != "" && o.Persist[1] != "" {
|
||||
// --overlay RWSRC WORKDIR
|
||||
*args = append(*args, awkwardArgs[Overlay], o.Persist[0], o.Persist[1])
|
||||
} else {
|
||||
// --ro-overlay
|
||||
*args = append(*args, awkwardArgs[ROOverlay])
|
||||
}
|
||||
} else {
|
||||
// --tmp-overlay
|
||||
*args = append(*args, awkwardArgs[TmpOverlay])
|
||||
}
|
||||
|
||||
// DEST
|
||||
*args = append(*args, o.Dest)
|
||||
}
|
||||
|
||||
type SymlinkConfig [2]string
|
||||
|
||||
func (s SymlinkConfig) Path() string {
|
||||
return s[1]
|
||||
}
|
||||
|
||||
func (s SymlinkConfig) Len() int {
|
||||
return 3
|
||||
}
|
||||
|
||||
func (s SymlinkConfig) Append(args *[]string) {
|
||||
*args = append(*args, awkwardArgs[Symlink], s[0], s[1])
|
||||
}
|
||||
|
||||
type ChmodConfig map[string]os.FileMode
|
||||
|
||||
func (c ChmodConfig) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
func (c ChmodConfig) Append(args *[]string) {
|
||||
for path, mode := range c {
|
||||
*args = append(*args, pairArgs[Chmod], strconv.FormatInt(int64(mode), 8), path)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package bwrap_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
@ -13,6 +14,83 @@ func TestConfig_Args(t *testing.T) {
|
||||
conf *bwrap.Config
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "bind",
|
||||
conf: (new(bwrap.Config)).
|
||||
Bind("/etc", "/.fortify/etc").
|
||||
Bind("/etc", "/.fortify/etc", true).
|
||||
Bind("/run", "/.fortify/run", false, true).
|
||||
Bind("/sys/devices", "/.fortify/sys/devices", true, true).
|
||||
Bind("/dev/dri", "/.fortify/dev/dri", false, true, true).
|
||||
Bind("/dev/dri", "/.fortify/dev/dri", true, true, true),
|
||||
want: []string{
|
||||
"--unshare-all", "--unshare-user",
|
||||
"--disable-userns", "--assert-userns-disabled",
|
||||
// Bind("/etc", "/.fortify/etc")
|
||||
"--ro-bind", "/etc", "/.fortify/etc",
|
||||
// Bind("/etc", "/.fortify/etc", true)
|
||||
"--ro-bind-try", "/etc", "/.fortify/etc",
|
||||
// Bind("/run", "/.fortify/run", false, true)
|
||||
"--bind", "/run", "/.fortify/run",
|
||||
// Bind("/sys/devices", "/.fortify/sys/devices", true, true)
|
||||
"--bind-try", "/sys/devices", "/.fortify/sys/devices",
|
||||
// Bind("/dev/dri", "/.fortify/dev/dri", false, true, true)
|
||||
"--dev-bind", "/dev/dri", "/.fortify/dev/dri",
|
||||
// Bind("/dev/dri", "/.fortify/dev/dri", true, true, true)
|
||||
"--dev-bind-try", "/dev/dri", "/.fortify/dev/dri",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dir remount-ro proc dev mqueue",
|
||||
conf: (new(bwrap.Config)).
|
||||
Dir("/.fortify").
|
||||
RemountRO("/home").
|
||||
Procfs("/proc").
|
||||
DevTmpfs("/dev").
|
||||
Mqueue("/dev/mqueue"),
|
||||
want: []string{
|
||||
"--unshare-all", "--unshare-user",
|
||||
"--disable-userns", "--assert-userns-disabled",
|
||||
// Dir("/.fortify")
|
||||
"--dir", "/.fortify",
|
||||
// RemountRO("/home")
|
||||
"--remount-ro", "/home",
|
||||
// Procfs("/proc")
|
||||
"--proc", "/proc",
|
||||
// DevTmpfs("/dev")
|
||||
"--dev", "/dev",
|
||||
// Mqueue("/dev/mqueue")
|
||||
"--mqueue", "/dev/mqueue",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tmpfs",
|
||||
conf: (new(bwrap.Config)).
|
||||
Tmpfs("/run/user", 8192).
|
||||
Tmpfs("/run/dbus", 8192, 0755),
|
||||
want: []string{
|
||||
"--unshare-all", "--unshare-user",
|
||||
"--disable-userns", "--assert-userns-disabled",
|
||||
// Tmpfs("/run/user", 8192)
|
||||
"--size", "8192", "--tmpfs", "/run/user",
|
||||
// Tmpfs("/run/dbus", 8192, 0755)
|
||||
"--perms", "755", "--size", "8192", "--tmpfs", "/run/dbus",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "symlink",
|
||||
conf: (new(bwrap.Config)).
|
||||
Symlink("/.fortify/sbin/init", "/sbin/init").
|
||||
Symlink("/.fortify/sbin/init", "/sbin/init", 0755),
|
||||
want: []string{
|
||||
"--unshare-all", "--unshare-user",
|
||||
"--disable-userns", "--assert-userns-disabled",
|
||||
// Symlink("/.fortify/sbin/init", "/sbin/init")
|
||||
"--symlink", "/.fortify/sbin/init", "/sbin/init",
|
||||
// Symlink("/.fortify/sbin/init", "/sbin/init", 0755)
|
||||
"--perms", "755", "--symlink", "/.fortify/sbin/init", "/sbin/init",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "overlayfs",
|
||||
conf: (new(bwrap.Config)).
|
||||
@ -32,6 +110,64 @@ func TestConfig_Args(t *testing.T) {
|
||||
"--overlay", "/data/data/org.chromium.Chromium/overlay/rwsrc", "/data/data/org.chromium.Chromium/workdir", "/nix",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unshare",
|
||||
conf: &bwrap.Config{Unshare: &bwrap.UnshareConfig{
|
||||
User: false,
|
||||
IPC: false,
|
||||
PID: false,
|
||||
Net: false,
|
||||
UTS: false,
|
||||
CGroup: false,
|
||||
}},
|
||||
want: []string{"--disable-userns", "--assert-userns-disabled"},
|
||||
},
|
||||
{
|
||||
name: "uid gid sync",
|
||||
conf: (new(bwrap.Config)).
|
||||
SetUID(1971).
|
||||
SetGID(100).
|
||||
SetSync(os.Stdin),
|
||||
want: []string{
|
||||
"--unshare-all", "--unshare-user",
|
||||
"--disable-userns", "--assert-userns-disabled",
|
||||
// SetUID(1971)
|
||||
"--uid", "1971",
|
||||
// SetGID(100)
|
||||
"--gid", "100",
|
||||
// SetSync(os.Stdin)
|
||||
// this is set when the process is created
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hostname chdir setenv unsetenv lockfile chmod",
|
||||
conf: &bwrap.Config{
|
||||
Hostname: "fortify",
|
||||
Chdir: "/.fortify",
|
||||
SetEnv: map[string]string{"FORTIFY_INIT": "/.fortify/sbin/init"},
|
||||
UnsetEnv: []string{"HOME", "HOST"},
|
||||
LockFile: []string{"/.fortify/lock"},
|
||||
Chmod: map[string]os.FileMode{"/.fortify/sbin/init": 0755},
|
||||
},
|
||||
want: []string{
|
||||
"--unshare-all", "--unshare-user",
|
||||
"--disable-userns", "--assert-userns-disabled",
|
||||
// Hostname: "fortify"
|
||||
"--hostname", "fortify",
|
||||
// Chdir: "/.fortify"
|
||||
"--chdir", "/.fortify",
|
||||
// UnsetEnv: []string{"HOME", "HOST"}
|
||||
"--unsetenv", "HOME",
|
||||
"--unsetenv", "HOST",
|
||||
// LockFile: []string{"/.fortify/lock"},
|
||||
"--lock-file", "/.fortify/lock",
|
||||
// SetEnv: map[string]string{"FORTIFY_INIT": "/.fortify/sbin/init"}
|
||||
"--setenv", "FORTIFY_INIT", "/.fortify/sbin/init",
|
||||
// Chmod: map[string]os.FileMode{"/.fortify/sbin/init": 0755}
|
||||
"--chmod", "755", "/.fortify/sbin/init",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "xdg-dbus-proxy constraint sample",
|
||||
conf: (&bwrap.Config{
|
||||
@ -90,148 +226,6 @@ func TestConfig_Args(t *testing.T) {
|
||||
"--ro-bind", "/etc", "/etc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fortify permissive default nixos",
|
||||
conf: (&bwrap.Config{
|
||||
Unshare: nil,
|
||||
Net: true,
|
||||
UserNS: true,
|
||||
Clearenv: true,
|
||||
SetEnv: map[string]string{
|
||||
"HOME": "/home/chronos",
|
||||
"TERM": "xterm-256color",
|
||||
"FORTIFY_INIT": "3",
|
||||
"XDG_RUNTIME_DIR": "/run/user/150",
|
||||
"XDG_SESSION_CLASS": "user",
|
||||
"XDG_SESSION_TYPE": "tty",
|
||||
"SHELL": "/run/current-system/sw/bin/zsh",
|
||||
"USER": "chronos",
|
||||
},
|
||||
DieWithParent: true,
|
||||
AsInit: true,
|
||||
}).SetUID(65534).SetGID(65534).
|
||||
Procfs("/proc").DevTmpfs("/dev").Mqueue("/dev/mqueue").
|
||||
Bind("/bin", "/bin", false, true).
|
||||
Bind("/boot", "/boot", false, true).
|
||||
Bind("/etc", "/etc", false, true).
|
||||
Bind("/home", "/home", false, true).
|
||||
Bind("/lib", "/lib", false, true).
|
||||
Bind("/lib64", "/lib64", false, true).
|
||||
Bind("/nix", "/nix", false, true).
|
||||
Bind("/root", "/root", false, true).
|
||||
Bind("/srv", "/srv", false, true).
|
||||
Bind("/sys", "/sys", false, true).
|
||||
Bind("/usr", "/usr", false, true).
|
||||
Bind("/var", "/var", false, true).
|
||||
Bind("/run/NetworkManager", "/run/NetworkManager", 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/nginx", "/run/nginx", 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/syncoid", "/run/syncoid", 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("/tmp/fortify.1971/tmpdir/150", "/tmp", false, true).
|
||||
Tmpfs("/tmp/fortify.1971", 1048576).
|
||||
Tmpfs("/run/user", 1048576).
|
||||
Tmpfs("/run/user/150", 8388608).
|
||||
Bind("/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/passwd", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/passwd").
|
||||
Bind("/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/group", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/group").
|
||||
Bind("/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/passwd", "/etc/passwd").
|
||||
Bind("/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/group", "/etc/group").
|
||||
Tmpfs("/var/run/nscd", 8192),
|
||||
want: []string{
|
||||
"--unshare-all", "--unshare-user", "--share-net",
|
||||
"--clearenv", "--die-with-parent", "--as-pid-1",
|
||||
"--uid", "65534",
|
||||
"--gid", "65534",
|
||||
"--setenv", "FORTIFY_INIT", "3",
|
||||
"--setenv", "HOME", "/home/chronos",
|
||||
"--setenv", "SHELL", "/run/current-system/sw/bin/zsh",
|
||||
"--setenv", "TERM", "xterm-256color",
|
||||
"--setenv", "USER", "chronos",
|
||||
"--setenv", "XDG_RUNTIME_DIR", "/run/user/150",
|
||||
"--setenv", "XDG_SESSION_CLASS", "user",
|
||||
"--setenv", "XDG_SESSION_TYPE", "tty",
|
||||
"--proc", "/proc", "--dev", "/dev",
|
||||
"--mqueue", "/dev/mqueue",
|
||||
"--bind", "/bin", "/bin",
|
||||
"--bind", "/boot", "/boot",
|
||||
"--bind", "/etc", "/etc",
|
||||
"--bind", "/home", "/home",
|
||||
"--bind", "/lib", "/lib",
|
||||
"--bind", "/lib64", "/lib64",
|
||||
"--bind", "/nix", "/nix",
|
||||
"--bind", "/root", "/root",
|
||||
"--bind", "/srv", "/srv",
|
||||
"--bind", "/sys", "/sys",
|
||||
"--bind", "/usr", "/usr",
|
||||
"--bind", "/var", "/var",
|
||||
"--bind", "/run/NetworkManager", "/run/NetworkManager",
|
||||
"--bind", "/run/agetty.reload", "/run/agetty.reload",
|
||||
"--bind", "/run/binfmt", "/run/binfmt",
|
||||
"--bind", "/run/booted-system", "/run/booted-system",
|
||||
"--bind", "/run/credentials", "/run/credentials",
|
||||
"--bind", "/run/cryptsetup", "/run/cryptsetup",
|
||||
"--bind", "/run/current-system", "/run/current-system",
|
||||
"--bind", "/run/host", "/run/host",
|
||||
"--bind", "/run/keys", "/run/keys",
|
||||
"--bind", "/run/libvirt", "/run/libvirt",
|
||||
"--bind", "/run/libvirtd.pid", "/run/libvirtd.pid",
|
||||
"--bind", "/run/lock", "/run/lock",
|
||||
"--bind", "/run/log", "/run/log",
|
||||
"--bind", "/run/lvm", "/run/lvm",
|
||||
"--bind", "/run/mount", "/run/mount",
|
||||
"--bind", "/run/nginx", "/run/nginx",
|
||||
"--bind", "/run/nscd", "/run/nscd",
|
||||
"--bind", "/run/opengl-driver", "/run/opengl-driver",
|
||||
"--bind", "/run/pppd", "/run/pppd",
|
||||
"--bind", "/run/resolvconf", "/run/resolvconf",
|
||||
"--bind", "/run/sddm", "/run/sddm",
|
||||
"--bind", "/run/syncoid", "/run/syncoid",
|
||||
"--bind", "/run/systemd", "/run/systemd",
|
||||
"--bind", "/run/tmpfiles.d", "/run/tmpfiles.d",
|
||||
"--bind", "/run/udev", "/run/udev",
|
||||
"--bind", "/run/udisks2", "/run/udisks2",
|
||||
"--bind", "/run/utmp", "/run/utmp",
|
||||
"--bind", "/run/virtlogd.pid", "/run/virtlogd.pid",
|
||||
"--bind", "/run/wrappers", "/run/wrappers",
|
||||
"--bind", "/run/zed.pid", "/run/zed.pid",
|
||||
"--bind", "/run/zed.state", "/run/zed.state",
|
||||
"--bind", "/tmp/fortify.1971/tmpdir/150", "/tmp",
|
||||
"--size", "1048576", "--tmpfs", "/tmp/fortify.1971",
|
||||
"--size", "1048576", "--tmpfs", "/run/user",
|
||||
"--size", "8388608", "--tmpfs", "/run/user/150",
|
||||
"--ro-bind", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/passwd", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/passwd",
|
||||
"--ro-bind", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/group", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/group",
|
||||
"--ro-bind", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/passwd", "/etc/passwd",
|
||||
"--ro-bind", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/group", "/etc/group",
|
||||
"--size", "8192", "--tmpfs", "/var/run/nscd",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -241,4 +235,21 @@ func TestConfig_Args(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// test persist validation
|
||||
t.Run("invalid persist", func(t *testing.T) {
|
||||
defer func() {
|
||||
wantPanic := "persist called without required paths"
|
||||
if r := recover(); r != wantPanic {
|
||||
t.Errorf("Persist() panic = %v; wantPanic %v", r, wantPanic)
|
||||
}
|
||||
}()
|
||||
(new(bwrap.Config)).Persist("/run", "", "")
|
||||
})
|
||||
|
||||
t.Run("sync file", func(t *testing.T) {
|
||||
if s := (new(bwrap.Config)).SetSync(os.Stdout).Sync(); s != os.Stdout {
|
||||
t.Errorf("Sync() = %v", s)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
223
helper/bwrap/sequential.go
Normal file
223
helper/bwrap/sequential.go
Normal file
@ -0,0 +1,223 @@
|
||||
package bwrap
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gob.Register(new(PermConfig[SymlinkConfig]))
|
||||
gob.Register(new(PermConfig[*TmpfsConfig]))
|
||||
gob.Register(new(OverlayConfig))
|
||||
}
|
||||
|
||||
type PositionalArg int
|
||||
|
||||
func (p PositionalArg) Unwrap() string {
|
||||
return positionalArgs[p]
|
||||
}
|
||||
|
||||
const (
|
||||
Tmpfs PositionalArg = iota
|
||||
Symlink
|
||||
|
||||
Bind
|
||||
BindTry
|
||||
DevBind
|
||||
DevBindTry
|
||||
ROBind
|
||||
ROBindTry
|
||||
|
||||
Chmod
|
||||
Dir
|
||||
RemountRO
|
||||
Procfs
|
||||
DevTmpfs
|
||||
Mqueue
|
||||
|
||||
Perms
|
||||
Size
|
||||
|
||||
OverlaySrc
|
||||
Overlay
|
||||
TmpOverlay
|
||||
ROOverlay
|
||||
)
|
||||
|
||||
var positionalArgs = [...]string{
|
||||
Tmpfs: "--tmpfs",
|
||||
Symlink: "--symlink",
|
||||
|
||||
Bind: "--bind",
|
||||
BindTry: "--bind-try",
|
||||
DevBind: "--dev-bind",
|
||||
DevBindTry: "--dev-bind-try",
|
||||
ROBind: "--ro-bind",
|
||||
ROBindTry: "--ro-bind-try",
|
||||
|
||||
Chmod: "--chmod",
|
||||
Dir: "--dir",
|
||||
RemountRO: "--remount-ro",
|
||||
Procfs: "--proc",
|
||||
DevTmpfs: "--dev",
|
||||
Mqueue: "--mqueue",
|
||||
|
||||
Perms: "--perms",
|
||||
Size: "--size",
|
||||
|
||||
OverlaySrc: "--overlay-src",
|
||||
Overlay: "--overlay",
|
||||
TmpOverlay: "--tmp-overlay",
|
||||
ROOverlay: "--ro-overlay",
|
||||
}
|
||||
|
||||
type PermConfig[T FSBuilder] struct {
|
||||
// set permissions of next argument
|
||||
// (--perms OCTAL)
|
||||
Mode *os.FileMode `json:"mode,omitempty"`
|
||||
// path to get the new permission
|
||||
// (--bind-data, --file, etc.)
|
||||
Inner T `json:"path"`
|
||||
}
|
||||
|
||||
func (p *PermConfig[T]) Path() string {
|
||||
return p.Inner.Path()
|
||||
}
|
||||
|
||||
func (p *PermConfig[T]) Len() int {
|
||||
if p.Mode != nil {
|
||||
return p.Inner.Len() + 2
|
||||
} else {
|
||||
return p.Inner.Len()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PermConfig[T]) Append(args *[]string) {
|
||||
if p.Mode != nil {
|
||||
*args = append(*args, Perms.Unwrap(), strconv.FormatInt(int64(*p.Mode), 8))
|
||||
}
|
||||
p.Inner.Append(args)
|
||||
}
|
||||
|
||||
type TmpfsConfig struct {
|
||||
// set size of tmpfs
|
||||
// (--size BYTES)
|
||||
Size int `json:"size,omitempty"`
|
||||
// mount point of new tmpfs
|
||||
// (--tmpfs DEST)
|
||||
Dir string `json:"dir"`
|
||||
}
|
||||
|
||||
func (t *TmpfsConfig) Path() string {
|
||||
return t.Dir
|
||||
}
|
||||
|
||||
func (t *TmpfsConfig) Len() int {
|
||||
if t.Size > 0 {
|
||||
return 4
|
||||
} else {
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TmpfsConfig) Append(args *[]string) {
|
||||
if t.Size > 0 {
|
||||
*args = append(*args, Size.Unwrap(), strconv.Itoa(t.Size))
|
||||
}
|
||||
*args = append(*args, Tmpfs.Unwrap(), t.Dir)
|
||||
}
|
||||
|
||||
type OverlayConfig struct {
|
||||
/*
|
||||
read files from SRC in the following overlay
|
||||
(--overlay-src SRC)
|
||||
*/
|
||||
Src []string `json:"src,omitempty"`
|
||||
|
||||
/*
|
||||
mount overlayfs on DEST, with RWSRC as the host path for writes and
|
||||
WORKDIR an empty directory on the same filesystem as RWSRC
|
||||
(--overlay RWSRC WORKDIR DEST)
|
||||
|
||||
if nil, mount overlayfs on DEST, with writes going to an invisible tmpfs
|
||||
(--tmp-overlay DEST)
|
||||
|
||||
if either strings are empty, mount overlayfs read-only on DEST
|
||||
(--ro-overlay DEST)
|
||||
*/
|
||||
Persist *[2]string `json:"persist,omitempty"`
|
||||
|
||||
/*
|
||||
--overlay RWSRC WORKDIR DEST
|
||||
|
||||
--tmp-overlay DEST
|
||||
|
||||
--ro-overlay DEST
|
||||
*/
|
||||
Dest string `json:"dest"`
|
||||
}
|
||||
|
||||
func (o *OverlayConfig) Path() string {
|
||||
return o.Dest
|
||||
}
|
||||
|
||||
func (o *OverlayConfig) Len() int {
|
||||
// (--tmp-overlay DEST) or (--ro-overlay DEST)
|
||||
p := 2
|
||||
// (--overlay RWSRC WORKDIR DEST)
|
||||
if o.Persist != nil && o.Persist[0] != "" && o.Persist[1] != "" {
|
||||
p = 4
|
||||
}
|
||||
|
||||
return p + len(o.Src)*2
|
||||
}
|
||||
|
||||
func (o *OverlayConfig) Append(args *[]string) {
|
||||
// --overlay-src SRC
|
||||
for _, src := range o.Src {
|
||||
*args = append(*args, OverlaySrc.Unwrap(), src)
|
||||
}
|
||||
|
||||
if o.Persist != nil {
|
||||
if o.Persist[0] != "" && o.Persist[1] != "" {
|
||||
// --overlay RWSRC WORKDIR
|
||||
*args = append(*args, Overlay.Unwrap(), o.Persist[0], o.Persist[1])
|
||||
} else {
|
||||
// --ro-overlay
|
||||
*args = append(*args, ROOverlay.Unwrap())
|
||||
}
|
||||
} else {
|
||||
// --tmp-overlay
|
||||
*args = append(*args, TmpOverlay.Unwrap())
|
||||
}
|
||||
|
||||
// DEST
|
||||
*args = append(*args, o.Dest)
|
||||
}
|
||||
|
||||
type SymlinkConfig [2]string
|
||||
|
||||
func (s SymlinkConfig) Path() string {
|
||||
return s[1]
|
||||
}
|
||||
|
||||
func (s SymlinkConfig) Len() int {
|
||||
return 3
|
||||
}
|
||||
|
||||
func (s SymlinkConfig) Append(args *[]string) {
|
||||
*args = append(*args, Symlink.Unwrap(), s[0], s[1])
|
||||
}
|
||||
|
||||
type ChmodConfig map[string]os.FileMode
|
||||
|
||||
func (c ChmodConfig) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
func (c ChmodConfig) Append(args *[]string) {
|
||||
for path, mode := range c {
|
||||
*args = append(*args, Chmod.Unwrap(), strconv.FormatInt(int64(mode), 8), path)
|
||||
}
|
||||
}
|
249
helper/bwrap/static.go
Normal file
249
helper/bwrap/static.go
Normal file
@ -0,0 +1,249 @@
|
||||
package bwrap
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
/*
|
||||
static boolean args
|
||||
*/
|
||||
|
||||
type BoolArg int
|
||||
|
||||
func (b BoolArg) Unwrap() []string {
|
||||
return boolArgs[b]
|
||||
}
|
||||
|
||||
const (
|
||||
UnshareAll BoolArg = iota
|
||||
UnshareUser
|
||||
UnshareIPC
|
||||
UnsharePID
|
||||
UnshareNet
|
||||
UnshareUTS
|
||||
UnshareCGroup
|
||||
ShareNet
|
||||
|
||||
UserNS
|
||||
Clearenv
|
||||
|
||||
NewSession
|
||||
DieWithParent
|
||||
AsInit
|
||||
)
|
||||
|
||||
var boolArgs = [...][]string{
|
||||
UnshareAll: {"--unshare-all", "--unshare-user"},
|
||||
UnshareUser: {"--unshare-user"},
|
||||
UnshareIPC: {"--unshare-ipc"},
|
||||
UnsharePID: {"--unshare-pid"},
|
||||
UnshareNet: {"--unshare-net"},
|
||||
UnshareUTS: {"--unshare-uts"},
|
||||
UnshareCGroup: {"--unshare-cgroup"},
|
||||
ShareNet: {"--share-net"},
|
||||
|
||||
UserNS: {"--disable-userns", "--assert-userns-disabled"},
|
||||
Clearenv: {"--clearenv"},
|
||||
|
||||
NewSession: {"--new-session"},
|
||||
DieWithParent: {"--die-with-parent"},
|
||||
AsInit: {"--as-pid-1"},
|
||||
}
|
||||
|
||||
func (c *Config) boolArgs() Builder {
|
||||
b := boolArg{
|
||||
UserNS: !c.UserNS,
|
||||
Clearenv: c.Clearenv,
|
||||
|
||||
NewSession: c.NewSession,
|
||||
DieWithParent: c.DieWithParent,
|
||||
AsInit: c.AsInit,
|
||||
}
|
||||
|
||||
if c.Unshare == nil {
|
||||
b[UnshareAll] = true
|
||||
b[ShareNet] = c.Net
|
||||
} else {
|
||||
b[UnshareUser] = c.Unshare.User
|
||||
b[UnshareIPC] = c.Unshare.IPC
|
||||
b[UnsharePID] = c.Unshare.PID
|
||||
b[UnshareNet] = c.Unshare.Net
|
||||
b[UnshareUTS] = c.Unshare.UTS
|
||||
b[UnshareCGroup] = c.Unshare.CGroup
|
||||
}
|
||||
|
||||
return &b
|
||||
}
|
||||
|
||||
type boolArg [len(boolArgs)]bool
|
||||
|
||||
func (b *boolArg) Len() (l int) {
|
||||
for i, v := range b {
|
||||
if v {
|
||||
l += len(boolArgs[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *boolArg) Append(args *[]string) {
|
||||
for i, v := range b {
|
||||
if v {
|
||||
*args = append(*args, BoolArg(i).Unwrap()...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
static integer args
|
||||
*/
|
||||
|
||||
type IntArg int
|
||||
|
||||
func (i IntArg) Unwrap() string {
|
||||
return intArgs[i]
|
||||
}
|
||||
|
||||
const (
|
||||
UID IntArg = iota
|
||||
GID
|
||||
)
|
||||
|
||||
var intArgs = [...]string{
|
||||
UID: "--uid",
|
||||
GID: "--gid",
|
||||
}
|
||||
|
||||
func (c *Config) intArgs() Builder {
|
||||
return &intArg{
|
||||
UID: c.UID,
|
||||
GID: c.GID,
|
||||
}
|
||||
}
|
||||
|
||||
type intArg [len(intArgs)]*int
|
||||
|
||||
func (n *intArg) Len() (l int) {
|
||||
for _, v := range n {
|
||||
if v != nil {
|
||||
l += 2
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (n *intArg) Append(args *[]string) {
|
||||
for i, v := range n {
|
||||
if v != nil {
|
||||
*args = append(*args, IntArg(i).Unwrap(), strconv.Itoa(*v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
static string args
|
||||
*/
|
||||
|
||||
type StringArg int
|
||||
|
||||
func (s StringArg) Unwrap() string {
|
||||
return stringArgs[s]
|
||||
}
|
||||
|
||||
const (
|
||||
Hostname StringArg = iota
|
||||
Chdir
|
||||
UnsetEnv
|
||||
LockFile
|
||||
)
|
||||
|
||||
var stringArgs = [...]string{
|
||||
Hostname: "--hostname",
|
||||
Chdir: "--chdir",
|
||||
UnsetEnv: "--unsetenv",
|
||||
LockFile: "--lock-file",
|
||||
}
|
||||
|
||||
func (c *Config) stringArgs() Builder {
|
||||
n := stringArg{
|
||||
UnsetEnv: c.UnsetEnv,
|
||||
LockFile: c.LockFile,
|
||||
}
|
||||
|
||||
if c.Hostname != "" {
|
||||
n[Hostname] = []string{c.Hostname}
|
||||
}
|
||||
if c.Chdir != "" {
|
||||
n[Chdir] = []string{c.Chdir}
|
||||
}
|
||||
|
||||
return &n
|
||||
}
|
||||
|
||||
type stringArg [len(stringArgs)][]string
|
||||
|
||||
func (s *stringArg) Len() (l int) {
|
||||
for _, arg := range s {
|
||||
l += len(arg) * 2
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *stringArg) Append(args *[]string) {
|
||||
for i, arg := range s {
|
||||
for _, v := range arg {
|
||||
*args = append(*args, StringArg(i).Unwrap(), v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
static pair args
|
||||
*/
|
||||
|
||||
type PairArg int
|
||||
|
||||
func (p PairArg) Unwrap() string {
|
||||
return pairArgs[p]
|
||||
}
|
||||
|
||||
const (
|
||||
SetEnv PairArg = iota
|
||||
)
|
||||
|
||||
var pairArgs = [...]string{
|
||||
SetEnv: "--setenv",
|
||||
}
|
||||
|
||||
func (c *Config) pairArgs() Builder {
|
||||
var n pairArg
|
||||
n[SetEnv] = make([][2]string, len(c.SetEnv))
|
||||
keys := make([]string, 0, len(c.SetEnv))
|
||||
for k := range c.SetEnv {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
slices.Sort(keys)
|
||||
for i, k := range keys {
|
||||
n[SetEnv][i] = [2]string{k, c.SetEnv[k]}
|
||||
}
|
||||
|
||||
return &n
|
||||
}
|
||||
|
||||
type pairArg [len(pairArgs)][][2]string
|
||||
|
||||
func (p *pairArg) Len() (l int) {
|
||||
for _, v := range p {
|
||||
l += len(v) * 3
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *pairArg) Append(args *[]string) {
|
||||
for i, arg := range p {
|
||||
for _, v := range arg {
|
||||
*args = append(*args, PairArg(i).Unwrap(), v[0], v[1])
|
||||
}
|
||||
}
|
||||
}
|
@ -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").
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user