Compare commits

..

No commits in common. "6c1205106d7ac9702464e274502303d2b6df1dc3" and "d6cf736abf3f7d34643af66fb8e62f071c2289f2" have entirely different histories.

24 changed files with 507 additions and 953 deletions

View File

@ -62,27 +62,16 @@ This adds the `environment.fortify` option:
{ {
environment.fortify = { environment.fortify = {
enable = true; enable = true;
stateDir = "/var/lib/fortify"; stateDir = "/var/lib/persist/module/fortify";
users = { users = {
alice = 0; alice = 0;
nixos = 10; nixos = 10;
}; };
commonPaths = [ apps = [
{ {
src = "/sdcard";
write = true;
}
];
extraHomeConfig = {
home.stateVersion = "23.05";
};
apps = {
"org.chromium.Chromium" = {
name = "chromium"; name = "chromium";
identity = 1; id = "org.chromium.Chromium";
packages = [ pkgs.chromium ]; packages = [ pkgs.chromium ];
userns = true; userns = true;
mapRealUid = true; mapRealUid = true;
@ -115,20 +104,16 @@ This adds the `environment.fortify` option:
broadcast = { }; broadcast = { };
}; };
}; };
}; }
{
"org.claws_mail.Claws-Mail" = {
name = "claws-mail"; name = "claws-mail";
identity = 2; id = "org.claws_mail.Claws-Mail";
packages = [ pkgs.claws-mail ]; packages = [ pkgs.claws-mail ];
gpu = false; gpu = false;
capability.pulse = false; capability.pulse = false;
}; }
{
"org.weechat" = {
name = "weechat"; name = "weechat";
identity = 3;
shareUid = true;
packages = [ pkgs.weechat ]; packages = [ pkgs.weechat ];
capability = { capability = {
wayland = false; wayland = false;
@ -136,12 +121,10 @@ This adds the `environment.fortify` option:
dbus = true; dbus = true;
pulse = false; pulse = false;
}; };
}; }
{
"dev.vencord.Vesktop" = {
name = "discord"; name = "discord";
identity = 3; id = "dev.vencord.Vesktop";
shareUid = true;
packages = [ pkgs.vesktop ]; packages = [ pkgs.vesktop ];
share = pkgs.vesktop; share = pkgs.vesktop;
command = "vesktop --ozone-platform-hint=wayland"; command = "vesktop --ozone-platform-hint=wayland";
@ -159,12 +142,9 @@ This adds the `environment.fortify` option:
}; };
system.filter = true; system.filter = true;
}; };
}; }
{
"io.looking-glass" = {
name = "looking-glass-client"; name = "looking-glass-client";
identity = 4;
useCommonPaths = false;
groups = [ "plugdev" ]; groups = [ "plugdev" ];
extraPaths = [ extraPaths = [
{ {
@ -175,8 +155,8 @@ This adds the `environment.fortify` option:
extraConfig = { extraConfig = {
programs.looking-glass-client.enable = true; programs.looking-glass-client.enable = true;
}; };
}; }
}; ];
}; };
} }
``` ```

View File

@ -55,8 +55,6 @@
stateDir = "/var/lib/fortify"; stateDir = "/var/lib/fortify";
users.alice = 0; users.alice = 0;
extraHomeConfig = { home-manager = _: _: { home.stateVersion = "23.05"; };
home.stateVersion = "23.05";
};
}; };
} }

View File

@ -7,24 +7,24 @@ _fortify_app() {
_fortify_run() { _fortify_run() {
_arguments \ _arguments \
'--id[Reverse-DNS style Application identifier, leave empty to inherit instance identifier]:id' \ '--id[App ID, leave empty to disable security context app_id]:id' \
'-a[Application identity]: :_numbers' \ '-a[Fortify application ID]: :_numbers' \
'-g[Groups inherited by all container processes]: :_groups' \ '-g[Groups inherited by the app process]: :_groups' \
'-d[Container home directory]: :_files -/' \ '-d[Application home directory]: :_files -/' \
'-u[Passwd user name within sandbox]: :_users' \ '-u[Passwd name within sandbox]: :_users' \
'--wayland[Enable connection to Wayland via security-context-v1]' \ '--wayland[Share Wayland socket]' \
'-X[Enable direct connection to X11]' \ '-X[Share X11 socket and allow connection]' \
'--dbus[Enable proxied connection to D-Bus]' \ '--dbus[Proxy D-Bus connection]' \
'--pulse[Enable direct connection to PulseAudio]' \ '--pulse[Share PulseAudio socket and cookie]' \
'--dbus-config[Path to session bus proxy config file]: :_files -g "*.json"' \ '--dbus-config[Path to D-Bus proxy config file]: :_files -g "*.json"' \
'--dbus-system[Path to system bus proxy config file]: :_files -g "*.json"' \ '--dbus-system[Path to system D-Bus proxy config file]: :_files -g "*.json"' \
'--mpris[Allow owning MPRIS D-Bus path]' \ '--mpris[Allow owning MPRIS D-Bus path]' \
'--dbus-log[Force buffered logging in the D-Bus proxy]' '--dbus-log[Force logging in the D-Bus proxy]'
} }
_fortify_ps() { _fortify_ps() {
_arguments \ _arguments \
'--short[List instances only]' '--short[Print instance id]'
} }
_fortify_show() { _fortify_show() {
@ -78,5 +78,5 @@ __fortify_instances() {
_arguments -C \ _arguments -C \
'-v[Verbose output]' \ '-v[Verbose output]' \
'--json[Serialise output in JSON when applicable]' \ '--json[Format output in JSON when applicable]' \
'*::fortify command:_fortify_commands' '*::fortify command:_fortify_commands'

View File

@ -5,12 +5,8 @@ import (
"errors" "errors"
"io" "io"
"os" "os"
"strings"
) )
// ProxyPair is an upstream dbus address and a downstream socket path.
type ProxyPair [2]string
type Config struct { type Config struct {
// See set 'see' policy for NAME (--see=NAME) // See set 'see' policy for NAME (--see=NAME)
See []string `json:"see"` See []string `json:"see"`
@ -28,62 +24,7 @@ type Config struct {
Filter bool `json:"filter"` Filter bool `json:"filter"`
} }
func (c *Config) interfaces(yield func(string) bool) { func (c *Config) Args(bus [2]string) (args []string) {
for _, iface := range c.See {
if !yield(iface) {
return
}
}
for _, iface := range c.Talk {
if !yield(iface) {
return
}
}
for _, iface := range c.Own {
if !yield(iface) {
return
}
}
for iface := range c.Call {
if !yield(iface) {
return
}
}
for iface := range c.Broadcast {
if !yield(iface) {
return
}
}
}
func (c *Config) checkInterfaces(segment string) error {
for iface := range c.interfaces {
/*
xdg-dbus-proxy fails without output when this condition is not met:
char *dot = strrchr (filter->interface, '.');
if (dot != NULL)
{
*dot = 0;
if (strcmp (dot + 1, "*") != 0)
filter->member = g_strdup (dot + 1);
}
trim ".*" since they are removed before searching for '.':
if (g_str_has_suffix (name, ".*"))
{
name[strlen (name) - 2] = 0;
wildcard = TRUE;
}
*/
if strings.IndexByte(strings.TrimSuffix(iface, ".*"), '.') == -1 {
return &BadInterfaceError{iface, segment}
}
}
return nil
}
func (c *Config) Args(bus ProxyPair) (args []string) {
argc := 2 + len(c.See) + len(c.Talk) + len(c.Own) + len(c.Call) + len(c.Broadcast) argc := 2 + len(c.See) + len(c.Talk) + len(c.Own) + len(c.Call) + len(c.Broadcast)
if c.Log { if c.Log {
argc++ argc++
@ -119,7 +60,9 @@ func (c *Config) Args(bus ProxyPair) (args []string) {
return return
} }
func (c *Config) Load(r io.Reader) error { return json.NewDecoder(r).Decode(&c) } func (c *Config) Load(r io.Reader) error {
return json.NewDecoder(r).Decode(&c)
}
// NewConfigFromFile opens the target config file at path and parses its contents into *Config. // NewConfigFromFile opens the target config file at path and parses its contents into *Config.
func NewConfigFromFile(path string) (*Config, error) { func NewConfigFromFile(path string) (*Config, error) {

View File

@ -5,7 +5,6 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
@ -20,21 +19,64 @@ import (
"git.gensokyo.uk/security/fortify/sandbox" "git.gensokyo.uk/security/fortify/sandbox"
) )
func TestFinalise(t *testing.T) { func TestNew(t *testing.T) {
if _, err := dbus.Finalise(dbus.ProxyPair{}, dbus.ProxyPair{}, nil, nil); !errors.Is(err, syscall.EBADE) { for _, tc := range [][2][2]string{
t.Errorf("Finalise: error = %v, want %v", {
err, syscall.EBADE) {"unix:path=/run/user/1971/bus", "/tmp/fortify.1971/1ca5d183ef4c99e74c3e544715f32702/bus"},
{"unix:path=/run/dbus/system_bus_socket", "/tmp/fortify.1971/1ca5d183ef4c99e74c3e544715f32702/system_bus_socket"},
},
{
{"unix:path=/run/user/1971/bus", "/tmp/fortify.1971/881ac3796ff3f3bf0a773824383187a0/bus"},
{"unix:path=/run/dbus/system_bus_socket", "/tmp/fortify.1971/881ac3796ff3f3bf0a773824383187a0/system_bus_socket"},
},
{
{"unix:path=/run/user/1971/bus", "/tmp/fortify.1971/3d1a5084520ef79c0c6a49a675bac701/bus"},
{"unix:path=/run/dbus/system_bus_socket", "/tmp/fortify.1971/3d1a5084520ef79c0c6a49a675bac701/system_bus_socket"},
},
{
{"unix:path=/run/user/1971/bus", "/tmp/fortify.1971/2a1639bab712799788ea0ff7aa280c35/bus"},
{"unix:path=/run/dbus/system_bus_socket", "/tmp/fortify.1971/2a1639bab712799788ea0ff7aa280c35/system_bus_socket"},
},
} {
t.Run("create instance for "+tc[0][0]+" and "+tc[1][0], func(t *testing.T) {
if got := dbus.New(tc[0], tc[1]); !got.CompareTestNew(tc[0], tc[1]) {
t.Errorf("New(%q, %q) = %v",
tc[0], tc[1],
got)
}
})
}
}
func TestProxy_Seal(t *testing.T) {
t.Run("double seal panic", func(t *testing.T) {
defer func() {
want := "dbus proxy sealed twice"
if r := recover(); r != want {
t.Errorf("Seal: panic = %q, want %q",
r, want)
}
}()
p := dbus.New([2]string{}, [2]string{})
_ = p.Seal(dbus.NewConfig("", true, false), nil)
_ = p.Seal(dbus.NewConfig("", true, false), nil)
})
ep := dbus.New([2]string{}, [2]string{})
if err := ep.Seal(nil, nil); !errors.Is(err, dbus.ErrConfig) {
t.Errorf("Seal(nil, nil) error = %v, want %v",
err, dbus.ErrConfig)
} }
for id, tc := range testCasePairs() { for id, tc := range testCasePairs() {
t.Run("create final for "+id, func(t *testing.T) { t.Run("create seal for "+id, func(t *testing.T) {
var wt io.WriterTo p := dbus.New(tc[0].bus, tc[1].bus)
if v, err := dbus.Finalise(tc[0].bus, tc[1].bus, tc[0].c, tc[1].c); (errors.Is(err, syscall.EINVAL)) != tc[0].wantErr { if err := p.Seal(tc[0].c, tc[1].c); (errors.Is(err, syscall.EINVAL)) != tc[0].wantErr {
t.Errorf("Finalise: error = %v, wantErr %v", t.Errorf("Seal(%p, %p) error = %v, wantErr %v",
tc[0].c, tc[1].c,
err, tc[0].wantErr) err, tc[0].wantErr)
return return
} else {
wt = v
} }
// rest of the tests happen for sealed instances // rest of the tests happen for sealed instances
@ -47,23 +89,25 @@ func TestFinalise(t *testing.T) {
args := append(tc[0].want, tc[1].want...) args := append(tc[0].want, tc[1].want...)
for _, arg := range args { for _, arg := range args {
want.WriteString(arg) want.WriteString(arg)
want.WriteByte(0) want.WriteByte('\x00')
} }
wt := p.AccessTestProxySeal()
got := new(strings.Builder) got := new(strings.Builder)
if _, err := wt.WriteTo(got); err != nil { if _, err := wt.WriteTo(got); err != nil {
t.Errorf("WriteTo: error = %v", err) t.Errorf("p.seal.WriteTo(): %v", err)
} }
if want.String() != got.String() { if want.String() != got.String() {
t.Errorf("Seal: %q, want %q", t.Errorf("Seal(%p, %p) seal = %v, want %v",
tc[0].c, tc[1].c,
got.String(), want.String()) got.String(), want.String())
} }
}) })
} }
} }
func TestProxyStartWaitCloseString(t *testing.T) { func TestProxy_Start_Wait_Close_String(t *testing.T) {
oldWaitDelay := helper.WaitDelay oldWaitDelay := helper.WaitDelay
helper.WaitDelay = 16 * time.Second helper.WaitDelay = 16 * time.Second
t.Cleanup(func() { helper.WaitDelay = oldWaitDelay }) t.Cleanup(func() { helper.WaitDelay = oldWaitDelay })
@ -72,62 +116,29 @@ func TestProxyStartWaitCloseString(t *testing.T) {
proxyName := dbus.ProxyName proxyName := dbus.ProxyName
dbus.ProxyName = os.Args[0] dbus.ProxyName = os.Args[0]
t.Cleanup(func() { dbus.ProxyName = proxyName }) t.Cleanup(func() { dbus.ProxyName = proxyName })
testProxyFinaliseStartWaitCloseString(t, true) testProxyStartWaitCloseString(t, true)
}) })
t.Run("direct", func(t *testing.T) { testProxyFinaliseStartWaitCloseString(t, false) }) t.Run("direct", func(t *testing.T) { testProxyStartWaitCloseString(t, false) })
} }
func testProxyFinaliseStartWaitCloseString(t *testing.T, useSandbox bool) { func testProxyStartWaitCloseString(t *testing.T, useSandbox bool) {
var p *dbus.Proxy
t.Run("string for nil proxy", func(t *testing.T) {
want := "(invalid dbus proxy)"
if got := p.String(); got != want {
t.Errorf("String: %q, want %q",
got, want)
}
})
t.Run("invalid start", func(t *testing.T) {
if !useSandbox {
p = dbus.NewDirect(context.TODO(), nil, nil)
} else {
p = dbus.New(context.TODO(), nil, nil)
}
if err := p.Start(); !errors.Is(err, syscall.ENOTRECOVERABLE) {
t.Errorf("Start: error = %q, wantErr %q",
err, syscall.ENOTRECOVERABLE)
return
}
})
for id, tc := range testCasePairs() { for id, tc := range testCasePairs() {
// this test does not test errors // this test does not test errors
if tc[0].wantErr { if tc[0].wantErr {
continue continue
} }
t.Run("proxy for "+id, func(t *testing.T) { t.Run("string for nil proxy", func(t *testing.T) {
var final *dbus.Final var p *dbus.Proxy
t.Run("finalise", func(t *testing.T) { want := "(invalid dbus proxy)"
if v, err := dbus.Finalise(tc[0].bus, tc[1].bus, tc[0].c, tc[1].c); err != nil { if got := p.String(); got != want {
t.Errorf("Finalise: error = %v, wantErr %v", t.Errorf("String() = %v, want %v",
err, tc[0].wantErr) got, want)
return
} else {
final = v
} }
}) })
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) t.Run("proxy for "+id, func(t *testing.T) {
defer cancel() p := dbus.New(tc[0].bus, tc[1].bus)
if !useSandbox {
p = dbus.NewDirect(ctx, final, nil)
} else {
p = dbus.New(ctx, final, nil)
}
p.CommandContext = func(ctx context.Context) (cmd *exec.Cmd) { p.CommandContext = func(ctx context.Context) (cmd *exec.Cmd) {
return exec.CommandContext(ctx, os.Args[0], "-test.v", return exec.CommandContext(ctx, os.Args[0], "-test.v",
"-test.run=TestHelperInit", "--", "init") "-test.run=TestHelperInit", "--", "init")
@ -152,26 +163,57 @@ func testProxyFinaliseStartWaitCloseString(t *testing.T, useSandbox bool) {
p.FilterF = func(v []byte) []byte { return bytes.SplitN(v, []byte("TestHelperInit\n"), 2)[1] } p.FilterF = func(v []byte) []byte { return bytes.SplitN(v, []byte("TestHelperInit\n"), 2)[1] }
output := new(strings.Builder) output := new(strings.Builder)
t.Run("invalid wait", func(t *testing.T) { t.Run("unsealed", func(t *testing.T) {
wantErr := "dbus: not started"
if err := p.Wait(); err == nil || err.Error() != wantErr {
t.Errorf("Wait: error = %v, wantErr %v",
err, wantErr)
}
})
t.Run("string", func(t *testing.T) { t.Run("string", func(t *testing.T) {
want := "(unused dbus proxy)" want := "(unsealed dbus proxy)"
if got := p.String(); got != want { if got := p.String(); got != want {
t.Errorf("String: %q, want %q", t.Errorf("String() = %v, want %v",
got, want) got, want)
return return
} }
}) })
t.Run("start", func(t *testing.T) { t.Run("start", func(t *testing.T) {
if err := p.Start(); err != nil { want := "proxy not sealed"
t.Fatalf("Start: error = %v", if err := p.Start(context.Background(), nil, useSandbox); err == nil || err.Error() != want {
t.Errorf("Start() error = %v, wantErr %q",
err, errors.New(want))
return
}
})
t.Run("wait", func(t *testing.T) {
wantErr := "dbus: not started"
if err := p.Wait(); err == nil || err.Error() != wantErr {
t.Errorf("Wait() error = %v, wantErr %v",
err, wantErr)
}
})
})
t.Run("seal with "+id, func(t *testing.T) {
if err := p.Seal(tc[0].c, tc[1].c); err != nil {
t.Errorf("Seal(%p, %p) error = %v, wantErr %v",
tc[0].c, tc[1].c,
err, tc[0].wantErr)
return
}
})
t.Run("sealed", func(t *testing.T) {
want := strings.Join(append(tc[0].want, tc[1].want...), " ")
if got := p.String(); got != want {
t.Errorf("String() = %v, want %v",
got, want)
return
}
t.Run("start", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := p.Start(ctx, output, useSandbox); err != nil {
t.Fatalf("Start(nil, nil) error = %v",
err) err)
} }
@ -181,23 +223,19 @@ func testProxyFinaliseStartWaitCloseString(t *testing.T, useSandbox bool) {
wantSubstr = fmt.Sprintf(`argv: ["%s" "-test.run=TestHelperStub" "--" "--args=3" "--fd=4"], flags: 0x0, seccomp: 0x3e`, os.Args[0]) wantSubstr = fmt.Sprintf(`argv: ["%s" "-test.run=TestHelperStub" "--" "--args=3" "--fd=4"], flags: 0x0, seccomp: 0x3e`, os.Args[0])
} }
if got := p.String(); !strings.Contains(got, wantSubstr) { if got := p.String(); !strings.Contains(got, wantSubstr) {
t.Errorf("String: %q, want %q", t.Errorf("String() = %v, want %v",
got, wantSubstr) p.String(), wantSubstr)
return return
} }
}) })
t.Run("wait", func(t *testing.T) { t.Run("wait", func(t *testing.T) {
done := make(chan struct{}) p.Close()
go func() {
if err := p.Wait(); err != nil { if err := p.Wait(); err != nil {
t.Errorf("Wait: error = %v\noutput: %s", t.Errorf("Wait() error = %v\noutput: %s",
err, output.String()) err, output.String())
} }
close(done) })
}()
p.Close()
<-done
}) })
}) })
}) })

View File

@ -1,13 +1,13 @@
package dbus package dbus
import ( import "io"
"context"
"io"
)
// NewDirect returns a new instance of [Proxy] with its sandbox disabled. // CompareTestNew provides TestNew with comparison access to unexported Proxy fields.
func NewDirect(ctx context.Context, final *Final, output io.Writer) *Proxy { func (p *Proxy) CompareTestNew(session, system [2]string) bool {
p := New(ctx, final, output) return session == p.session && system == p.system
p.useSandbox = false }
return p
// AccessTestProxySeal provides TestProxy_Seal with access to unexported Proxy seal field.
func (p *Proxy) AccessTestProxySeal() io.WriterTo {
return p.seal
} }

View File

@ -3,12 +3,14 @@ package dbus
import ( import (
"context" "context"
"errors" "errors"
"io"
"os" "os"
"os/exec" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"slices" "slices"
"strconv" "strconv"
"strings"
"syscall" "syscall"
"git.gensokyo.uk/security/fortify/helper" "git.gensokyo.uk/security/fortify/helper"
@ -17,30 +19,25 @@ import (
"git.gensokyo.uk/security/fortify/sandbox/seccomp" "git.gensokyo.uk/security/fortify/sandbox/seccomp"
) )
// Start starts and configures a D-Bus proxy process. // Start launches the D-Bus proxy.
func (p *Proxy) Start() error { func (p *Proxy) Start(ctx context.Context, output io.Writer, useSandbox bool) error {
if p.final == nil || p.final.WriterTo == nil { p.lock.Lock()
return syscall.ENOTRECOVERABLE defer p.lock.Unlock()
if p.seal == nil {
return errors.New("proxy not sealed")
} }
p.mu.Lock() var h helper.Helper
defer p.mu.Unlock()
p.pmu.Lock()
defer p.pmu.Unlock()
if p.cancel != nil || p.cause != nil { c, cancel := context.WithCancelCause(ctx)
return errors.New("dbus: already started") if !useSandbox {
} h = helper.NewDirect(c, p.name, p.seal, true, argF, func(cmd *exec.Cmd) {
ctx, cancel := context.WithCancelCause(p.ctx)
if !p.useSandbox {
p.helper = helper.NewDirect(ctx, p.name, p.final, true, argF, func(cmd *exec.Cmd) {
if p.CmdF != nil { if p.CmdF != nil {
p.CmdF(cmd) p.CmdF(cmd)
} }
if p.output != nil { if output != nil {
cmd.Stdout, cmd.Stderr = p.output, p.output cmd.Stdout, cmd.Stderr = output, output
} }
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
cmd.Env = make([]string, 0) cmd.Env = make([]string, 0)
@ -62,15 +59,15 @@ func (p *Proxy) Start() error {
libPaths = ldd.Path(entries) libPaths = ldd.Path(entries)
} }
p.helper = helper.New( h = helper.New(
ctx, toolPath, c, toolPath,
p.final, true, p.seal, true,
argF, func(container *sandbox.Container) { argF, func(container *sandbox.Container) {
container.Seccomp |= seccomp.FilterMultiarch container.Seccomp |= seccomp.FilterMultiarch
container.Hostname = "fortify-dbus" container.Hostname = "fortify-dbus"
container.CommandContext = p.CommandContext container.CommandContext = p.CommandContext
if p.output != nil { if output != nil {
container.Stdout, container.Stderr = p.output, p.output container.Stdout, container.Stderr = output, output
} }
if p.CmdF != nil { if p.CmdF != nil {
@ -84,17 +81,10 @@ func (p *Proxy) Start() error {
// upstream bus directories // upstream bus directories
upstreamPaths := make([]string, 0, 2) upstreamPaths := make([]string, 0, 2)
for _, addr := range [][]AddrEntry{p.final.SessionUpstream, p.final.SystemUpstream} { for _, as := range []string{p.session[0], p.system[0]} {
for _, ent := range addr { if len(as) > 0 && strings.HasPrefix(as, "unix:path=/") {
if ent.Method != "unix" { // leave / intact
continue upstreamPaths = append(upstreamPaths, path.Dir(as[10:]))
}
for _, pair := range ent.Values {
if pair[0] != "path" || !path.IsAbs(pair[1]) {
continue
}
upstreamPaths = append(upstreamPaths, path.Dir(pair[1]))
}
} }
} }
slices.Sort(upstreamPaths) slices.Sort(upstreamPaths)
@ -105,10 +95,10 @@ func (p *Proxy) Start() error {
// parent directories of bind paths // parent directories of bind paths
sockDirPaths := make([]string, 0, 2) sockDirPaths := make([]string, 0, 2)
if d := path.Dir(p.final.Session[1]); path.IsAbs(d) { if d := path.Dir(p.session[1]); path.IsAbs(d) {
sockDirPaths = append(sockDirPaths, d) sockDirPaths = append(sockDirPaths, d)
} }
if d := path.Dir(p.final.System[1]); path.IsAbs(d) { if d := path.Dir(p.system[1]); path.IsAbs(d) {
sockDirPaths = append(sockDirPaths, d) sockDirPaths = append(sockDirPaths, d)
} }
slices.Sort(sockDirPaths) slices.Sort(sockDirPaths)
@ -123,13 +113,14 @@ func (p *Proxy) Start() error {
}, nil) }, nil)
} }
if err := p.helper.Start(); err != nil { if err := h.Start(); err != nil {
cancel(err) cancel(err)
p.helper = nil
return err return err
} }
p.cancel, p.cause = cancel, func() error { return context.Cause(ctx) } p.helper = h
p.ctx = c
p.cancel = cancel
return nil return nil
} }
@ -137,30 +128,28 @@ var proxyClosed = errors.New("proxy closed")
// Wait blocks until xdg-dbus-proxy exits and releases resources. // Wait blocks until xdg-dbus-proxy exits and releases resources.
func (p *Proxy) Wait() error { func (p *Proxy) Wait() error {
p.mu.RLock() p.lock.RLock()
defer p.mu.RUnlock() defer p.lock.RUnlock()
p.pmu.RLock() if p.helper == nil {
if p.helper == nil || p.cancel == nil || p.cause == nil {
p.pmu.RUnlock()
return errors.New("dbus: not started") return errors.New("dbus: not started")
} }
errs := make([]error, 3) errs := make([]error, 3)
errs[0] = p.helper.Wait() errs[0] = p.helper.Wait()
if errors.Is(errs[0], context.Canceled) && if p.cancel == nil &&
errors.Is(p.cause(), proxyClosed) { errors.Is(errs[0], context.Canceled) &&
errors.Is(context.Cause(p.ctx), proxyClosed) {
errs[0] = nil errs[0] = nil
} }
p.pmu.RUnlock()
// ensure socket removal so ephemeral directory is empty at revert // ensure socket removal so ephemeral directory is empty at revert
if err := os.Remove(p.final.Session[1]); err != nil && !errors.Is(err, os.ErrNotExist) { if err := os.Remove(p.session[1]); err != nil && !errors.Is(err, os.ErrNotExist) {
errs[1] = err errs[1] = err
} }
if p.final.System[1] != "" { if p.sysP {
if err := os.Remove(p.final.System[1]); err != nil && !errors.Is(err, os.ErrNotExist) { if err := os.Remove(p.system[1]); err != nil && !errors.Is(err, os.ErrNotExist) {
errs[2] = err errs[2] = err
} }
} }
@ -170,13 +159,14 @@ func (p *Proxy) Wait() error {
// Close cancels the context passed to the helper instance attached to xdg-dbus-proxy. // Close cancels the context passed to the helper instance attached to xdg-dbus-proxy.
func (p *Proxy) Close() { func (p *Proxy) Close() {
p.pmu.Lock() p.lock.Lock()
defer p.pmu.Unlock() defer p.lock.Unlock()
if p.cancel == nil { if p.cancel == nil {
panic("dbus: not started") panic("dbus: not started")
} }
p.cancel(proxyClosed) p.cancel(proxyClosed)
p.cancel = nil
} }
func argF(argsFd, statFd int) []string { func argF(argsFd, statFd int) []string {

View File

@ -2,11 +2,11 @@ package dbus
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"os/exec" "os/exec"
"sync" "sync"
"syscall"
"git.gensokyo.uk/security/fortify/helper" "git.gensokyo.uk/security/fortify/helper"
) )
@ -15,103 +15,84 @@ import (
// Overriding ProxyName will only affect Proxy instance created after the change. // Overriding ProxyName will only affect Proxy instance created after the change.
var ProxyName = "xdg-dbus-proxy" var ProxyName = "xdg-dbus-proxy"
type BadInterfaceError struct { // Proxy holds references to a xdg-dbus-proxy process, and should never be copied.
Interface string // Once sealed, configuration changes will no longer be possible and attempting to do so will result in a panic.
Segment string
}
func (e *BadInterfaceError) Error() string {
return fmt.Sprintf("bad interface string %q in %s bus configuration", e.Interface, e.Segment)
}
// Proxy holds the state of a xdg-dbus-proxy process, and should never be copied.
type Proxy struct { type Proxy struct {
helper helper.Helper helper helper.Helper
ctx context.Context ctx context.Context
cancel context.CancelCauseFunc cancel context.CancelCauseFunc
cause func() error
final *Final
output io.Writer
useSandbox bool
name string name string
session [2]string
system [2]string
CmdF func(any) CmdF func(any)
sysP bool
CommandContext func(ctx context.Context) (cmd *exec.Cmd) CommandContext func(ctx context.Context) (cmd *exec.Cmd)
FilterF func([]byte) []byte FilterF func([]byte) []byte
mu, pmu sync.RWMutex seal io.WriterTo
lock sync.RWMutex
} }
func (p *Proxy) Session() [2]string { return p.session }
func (p *Proxy) System() [2]string { return p.system }
func (p *Proxy) Sealed() bool { p.lock.RLock(); defer p.lock.RUnlock(); return p.seal != nil }
var (
ErrConfig = errors.New("no configuration to seal")
)
func (p *Proxy) String() string { func (p *Proxy) String() string {
if p == nil { if p == nil {
return "(invalid dbus proxy)" return "(invalid dbus proxy)"
} }
p.mu.RLock() p.lock.RLock()
defer p.mu.RUnlock() defer p.lock.RUnlock()
if p.helper != nil { if p.helper != nil {
return p.helper.String() return p.helper.String()
} }
return "(unused dbus proxy)" if p.seal != nil {
return p.seal.(fmt.Stringer).String()
} }
// Final describes the outcome of a proxy configuration. return "(unsealed dbus proxy)"
type Final struct { }
Session, System ProxyPair
// parsed upstream address // Seal seals the Proxy instance.
SessionUpstream, SystemUpstream []AddrEntry func (p *Proxy) Seal(session, system *Config) error {
io.WriterTo p.lock.Lock()
defer p.lock.Unlock()
if p.seal != nil {
panic("dbus proxy sealed twice")
} }
// Finalise creates a checked argument writer for [Proxy].
func Finalise(sessionBus, systemBus ProxyPair, session, system *Config) (final *Final, err error) {
if session == nil && system == nil { if session == nil && system == nil {
return nil, syscall.EBADE return ErrConfig
} }
var args []string var args []string
if session != nil { if session != nil {
if err = session.checkInterfaces("session"); err != nil { args = append(args, session.Args(p.session)...)
return
}
args = append(args, session.Args(sessionBus)...)
} }
if system != nil { if system != nil {
if err = system.checkInterfaces("system"); err != nil { args = append(args, system.Args(p.system)...)
return p.sysP = true
} }
args = append(args, system.Args(systemBus)...) if seal, err := helper.NewCheckedArgs(args); err != nil {
return err
} else {
p.seal = seal
} }
final = &Final{Session: sessionBus, System: systemBus} return nil
final.WriterTo, err = helper.NewCheckedArgs(args)
if err != nil {
return
} }
if session != nil { // New returns a reference to a new unsealed Proxy.
final.SessionUpstream, err = Parse([]byte(final.Session[0])) func New(session, system [2]string) *Proxy {
if err != nil { return &Proxy{name: ProxyName, session: session, system: system}
return
}
}
if system != nil {
final.SystemUpstream, err = Parse([]byte(final.System[0]))
if err != nil {
return
}
}
return
}
// New returns a new instance of [Proxy].
func New(ctx context.Context, final *Final, output io.Writer) *Proxy {
return &Proxy{name: ProxyName, ctx: ctx, final: final, output: output, useSandbox: true}
} }

12
flake.lock generated
View File

@ -7,11 +7,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1746171682, "lastModified": 1742655702,
"narHash": "sha256-EyXUNSa+H+YvGVuQJP1nZskXAowxKYp79RNUsNdQTj4=", "narHash": "sha256-jbqlw4sPArFtNtA1s3kLg7/A4fzP4GLk9bGbtUJg0JQ=",
"owner": "nix-community", "owner": "nix-community",
"repo": "home-manager", "repo": "home-manager",
"rev": "50eee705bbdbac942074a8c120e8194185633675", "rev": "0948aeedc296f964140d9429223c7e4a0702a1ff",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -23,11 +23,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1746557022, "lastModified": 1743231893,
"narHash": "sha256-QkNoyEf6TbaTW5UZYX0OkwIJ/ZMeKSSoOMnSDPQuol0=", "narHash": "sha256-tpJsHMUPEhEnzySoQxx7+kA+KUtgWqvlcUBqROYNNt0=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "1d3aeb5a193b9ff13f63f4d9cc169fb88129f860", "rev": "c570c1f5304493cafe133b8d843c7c1c4a10d3a6",
"type": "github" "type": "github"
}, },
"original": { "original": {

26
main.go
View File

@ -72,7 +72,7 @@ func buildCommand(out io.Writer) command.Command {
return nil return nil
}). }).
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console"). Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output in JSON when applicable") Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output as JSON when applicable")
c.Command("shim", command.UsageInternal, func([]string) error { instance.ShimMain(); return errSuccess }) c.Command("shim", command.UsageInternal, func([]string) error { instance.ShimMain(); return errSuccess })
@ -205,31 +205,31 @@ func buildCommand(out io.Writer) command.Command {
panic("unreachable") panic("unreachable")
}). }).
Flag(&dbusConfigSession, "dbus-config", command.StringFlag("builtin"), Flag(&dbusConfigSession, "dbus-config", command.StringFlag("builtin"),
"Path to session bus proxy config file, or \"builtin\" for defaults"). "Path to D-Bus proxy config file, or \"builtin\" for defaults").
Flag(&dbusConfigSystem, "dbus-system", command.StringFlag("nil"), Flag(&dbusConfigSystem, "dbus-system", command.StringFlag("nil"),
"Path to system bus proxy config file, or \"nil\" to disable"). "Path to system D-Bus proxy config file, or \"nil\" to disable").
Flag(&mpris, "mpris", command.BoolFlag(false), Flag(&mpris, "mpris", command.BoolFlag(false),
"Allow owning MPRIS D-Bus path, has no effect if custom config is available"). "Allow owning MPRIS D-Bus path, has no effect if custom config is available").
Flag(&dbusVerbose, "dbus-log", command.BoolFlag(false), Flag(&dbusVerbose, "dbus-log", command.BoolFlag(false),
"Force buffered logging in the D-Bus proxy"). "Force logging in the D-Bus proxy").
Flag(&fid, "id", command.StringFlag(""), Flag(&fid, "id", command.StringFlag(""),
"Reverse-DNS style Application identifier, leave empty to inherit instance identifier"). "App ID, leave empty to disable security context app_id").
Flag(&aid, "a", command.IntFlag(0), Flag(&aid, "a", command.IntFlag(0),
"Application identity"). "Fortify application ID").
Flag(nil, "g", &groups, Flag(nil, "g", &groups,
"Groups inherited by all container processes"). "Groups inherited by the app process").
Flag(&homeDir, "d", command.StringFlag("os"), Flag(&homeDir, "d", command.StringFlag("os"),
"Container home directory"). "Application home directory").
Flag(&userName, "u", command.StringFlag("chronos"), Flag(&userName, "u", command.StringFlag("chronos"),
"Passwd user name within sandbox"). "Passwd name within sandbox").
Flag(&wayland, "wayland", command.BoolFlag(false), Flag(&wayland, "wayland", command.BoolFlag(false),
"Enable connection to Wayland via security-context-v1"). "Allow Wayland connections").
Flag(&x11, "X", command.BoolFlag(false), Flag(&x11, "X", command.BoolFlag(false),
"Enable direct connection to X11"). "Share X11 socket and allow connection").
Flag(&dBus, "dbus", command.BoolFlag(false), Flag(&dBus, "dbus", command.BoolFlag(false),
"Enable proxied connection to D-Bus"). "Proxy D-Bus connection").
Flag(&pulse, "pulse", command.BoolFlag(false), Flag(&pulse, "pulse", command.BoolFlag(false),
"Enable direct connection to PulseAudio") "Share PulseAudio socket and cookie")
} }
var showFlagShort bool var showFlagShort bool

View File

@ -36,31 +36,31 @@ Commands:
Usage: fortify run [-h | --help] [--dbus-config <value>] [--dbus-system <value>] [--mpris] [--dbus-log] [--id <value>] [-a <int>] [-g <value>] [-d <value>] [-u <value>] [--wayland] [-X] [--dbus] [--pulse] COMMAND [OPTIONS] Usage: fortify run [-h | --help] [--dbus-config <value>] [--dbus-system <value>] [--mpris] [--dbus-log] [--id <value>] [-a <int>] [-g <value>] [-d <value>] [-u <value>] [--wayland] [-X] [--dbus] [--pulse] COMMAND [OPTIONS]
Flags: Flags:
-X Enable direct connection to X11 -X Share X11 socket and allow connection
-a int -a int
Application identity Fortify application ID
-d string -d string
Container home directory (default "os") Application home directory (default "os")
-dbus -dbus
Enable proxied connection to D-Bus Proxy D-Bus connection
-dbus-config string -dbus-config string
Path to session bus proxy config file, or "builtin" for defaults (default "builtin") Path to D-Bus proxy config file, or "builtin" for defaults (default "builtin")
-dbus-log -dbus-log
Force buffered logging in the D-Bus proxy Force logging in the D-Bus proxy
-dbus-system string -dbus-system string
Path to system bus proxy config file, or "nil" to disable (default "nil") Path to system D-Bus proxy config file, or "nil" to disable (default "nil")
-g value -g value
Groups inherited by all container processes Groups inherited by the app process
-id string -id string
Reverse-DNS style Application identifier, leave empty to inherit instance identifier App ID, leave empty to disable security context app_id
-mpris -mpris
Allow owning MPRIS D-Bus path, has no effect if custom config is available Allow owning MPRIS D-Bus path, has no effect if custom config is available
-pulse -pulse
Enable direct connection to PulseAudio Share PulseAudio socket and cookie
-u string -u string
Passwd user name within sandbox (default "chronos") Passwd name within sandbox (default "chronos")
-wayland -wayland
Enable connection to Wayland via security-context-v1 Allow Wayland connections
`, `,
}, },

129
nixos.nix
View File

@ -8,10 +8,12 @@ packages:
let let
inherit (lib) inherit (lib)
lists
mkMerge mkMerge
mkIf mkIf
mapAttrs mapAttrs
mergeAttrsList
imap1
foldr
foldlAttrs foldlAttrs
optional optional
optionals optionals
@ -28,27 +30,6 @@ in
imports = [ (import ./options.nix packages) ]; imports = [ (import ./options.nix packages) ];
config = mkIf cfg.enable { config = mkIf cfg.enable {
assertions = [
(
let
conflictingApps = foldlAttrs (
acc: id: app:
(
acc
++ foldlAttrs (
acc': id': app':
if id == id' || app.shareUid && app'.shareUid || app.identity != app'.identity then acc' else acc' ++ [ id ]
) [ ] cfg.apps
)
) [ ] cfg.apps;
in
{
assertion = (lists.length conflictingApps) == 0;
message = "the following fortify apps have conflicting identities: " + (builtins.concatStringsSep ", " conflictingApps);
}
)
];
security.wrappers.fsu = { security.wrappers.fsu = {
source = "${cfg.fsuPackage}/bin/fsu"; source = "${cfg.fsuPackage}/bin/fsu";
setuid = true; setuid = true;
@ -68,19 +49,22 @@ in
home-manager = home-manager =
let let
privPackages = mapAttrs (username: fid: { privPackages = mapAttrs (username: fid: {
home.packages = foldlAttrs ( home.packages =
acc: id: app: let
[ # aid 0 is reserved
( wrappers = imap1 (
aid: app:
let let
extendDBusDefault = id: ext: { extendDBusDefault = id: ext: {
filter = true; filter = true;
talk = [ "org.freedesktop.Notifications" ] ++ ext.talk; talk = [ "org.freedesktop.Notifications" ] ++ ext.talk;
own = [ own =
(optionals (app.id != null) [
"${id}.*" "${id}.*"
"org.mpris.MediaPlayer2.${id}.*" "org.mpris.MediaPlayer2.${id}.*"
] ++ ext.own; ])
++ ext.own;
inherit (ext) call broadcast; inherit (ext) call broadcast;
}; };
@ -94,7 +78,7 @@ in
}; };
in in
{ {
session_bus = if app.dbus.session != null then (app.dbus.session (extendDBusDefault id)) else (extendDBusDefault id default); session_bus = if app.dbus.session != null then (app.dbus.session (extendDBusDefault app.id)) else (extendDBusDefault app.id default);
system_bus = app.dbus.system; system_bus = app.dbus.system;
}; };
command = if app.command == null then app.name else app.command; command = if app.command == null then app.name else app.command;
@ -103,6 +87,8 @@ in
isGraphical = if app.gpu != null then app.gpu else app.capability.wayland || app.capability.x11; isGraphical = if app.gpu != null then app.gpu else app.capability.wayland || app.capability.x11;
conf = { conf = {
inherit (app) id;
path = path =
if app.path == null then if app.path == null then
pkgs.writeScript "${app.name}-start" '' pkgs.writeScript "${app.name}-start" ''
@ -113,15 +99,16 @@ in
app.path; app.path;
args = if app.args == null then [ "${app.name}-start" ] else app.args; args = if app.args == null then [ "${app.name}-start" ] else app.args;
inherit id enablements; inherit enablements;
inherit (dbusConfig) session_bus system_bus; inherit (dbusConfig) session_bus system_bus;
direct_wayland = app.insecureWayland; direct_wayland = app.insecureWayland;
username = getsubname fid app.identity; username = getsubname fid aid;
data = getsubhome fid app.identity; data = getsubhome fid aid;
inherit (app) identity groups; identity = aid;
inherit (app) groups;
container = { container = {
inherit (app) inherit (app)
@ -159,6 +146,7 @@ in
] ]
++ optionals app.nix [ ++ optionals app.nix [
(mustBind "/nix/var") (mustBind "/nix/var")
(bind "/var/db/nix-channels")
] ]
++ optionals isGraphical [ ++ optionals isGraphical [
(devBind "/dev/dri") (devBind "/dev/dri")
@ -168,7 +156,6 @@ in
(devBind "/dev/nvidia-uvm-tools") (devBind "/dev/nvidia-uvm-tools")
(devBind "/dev/nvidia0") (devBind "/dev/nvidia0")
] ]
++ optionals app.useCommonPaths cfg.commonPaths
++ app.extraPaths; ++ app.extraPaths;
auto_etc = true; auto_etc = true;
cover = [ "/var/run/nscd" ]; cover = [ "/var/run/nscd" ];
@ -201,9 +188,10 @@ in
pkgs.writeShellScriptBin app.name '' pkgs.writeShellScriptBin app.name ''
exec fortify${if app.verbose then " -v" else ""} app ${pkgs.writeText "fortify-${app.name}.json" (builtins.toJSON conf)} $@ exec fortify${if app.verbose then " -v" else ""} app ${pkgs.writeText "fortify-${app.name}.json" (builtins.toJSON conf)} $@
'' ''
) ) cfg.apps;
] in
++ ( foldr (
app: acc:
let let
pkg = if app.share != null then app.share else pkgs.${app.name}; pkg = if app.share != null then app.share else pkgs.${app.name};
copy = source: "[ -d '${source}' ] && cp -Lrv '${source}' $out/share || true"; copy = source: "[ -d '${source}' ] && cp -Lrv '${source}' $out/share || true";
@ -223,33 +211,30 @@ in
fi fi
'' ''
) )
)
++ acc ++ acc
) [ cfg.package ] cfg.apps; ) (wrappers ++ [ cfg.package ]) cfg.apps;
}) cfg.users; }) cfg.users;
in in
{ {
useUserPackages = false; # prevent users.users entries from being added useUserPackages = false; # prevent users.users entries from being added
users = mkMerge ( users = foldlAttrs (
foldlAttrs (
acc: _: fid: acc: _: fid:
acc mkMerge [
++ foldlAttrs ( (mergeAttrsList (
acc': _: app: # aid 0 is reserved
acc' imap1 (aid: app: {
++ [ ${getsubname fid aid} = mkMerge [
{ (cfg.home-manager (getsubname fid aid) (getsubuid fid aid))
${getsubname fid app.identity} = mkMerge [
cfg.extraHomeConfig
app.extraConfig app.extraConfig
{ home.packages = app.packages; } { home.packages = app.packages; }
]; ];
} }) cfg.apps
))
{ ${getsubname fid 0} = cfg.home-manager (getsubname fid 0) (getsubuid fid 0); }
acc
] ]
) [ { ${getsubname fid 0} = cfg.extraHomeConfig; } ] cfg.apps ) privPackages cfg.users;
) [ privPackages ] cfg.users
);
}; };
users = users =
@ -265,27 +250,33 @@ in
getgroup = fid: aid: { gid = getsubuid fid aid; }; getgroup = fid: aid: { gid = getsubuid fid aid; };
in in
{ {
users = mkMerge ( users = foldlAttrs (
foldlAttrs (
acc: _: fid: acc: _: fid:
mkMerge [
(mergeAttrsList (
# aid 0 is reserved
imap1 (aid: _: {
${getsubname fid aid} = getuser fid aid;
}) cfg.apps
))
{ ${getsubname fid 0} = getuser fid 0; }
acc acc
++ foldlAttrs ( ]
acc': _: app: ) { } cfg.users;
acc' ++ [ { ${getsubname fid app.identity} = getuser fid app.identity; } ]
) [ { ${getsubname fid 0} = getuser fid 0; } ] cfg.apps
) [ ] cfg.users
);
groups = mkMerge ( groups = foldlAttrs (
foldlAttrs (
acc: _: fid: acc: _: fid:
mkMerge [
(mergeAttrsList (
# aid 0 is reserved
imap1 (aid: _: {
${getsubname fid aid} = getgroup fid aid;
}) cfg.apps
))
{ ${getsubname fid 0} = getgroup fid 0; }
acc acc
++ foldlAttrs ( ]
acc': _: app: ) { } cfg.users;
acc' ++ [ { ${getsubname fid app.identity} = getgroup fid app.identity; } ]
) [ { ${getsubname fid 0} = getgroup fid 0; } ] cfg.apps
) [ ] cfg.users
);
}; };
}; };
} }

View File

@ -35,27 +35,27 @@ package
*Default:* *Default:*
` <derivation fortify-static-x86_64-unknown-linux-musl-0.4.1> ` ` <derivation fortify-static-x86_64-unknown-linux-musl-0.4.0> `
## environment\.fortify\.apps ## environment\.fortify\.apps
Declaratively configured fortify apps\. Declarative fortify apps\.
*Type:* *Type:*
attribute set of (submodule) list of (submodule)
*Default:* *Default:*
` { } ` ` [ ] `
## environment\.fortify\.apps\.\<name>\.packages ## environment\.fortify\.apps\.\*\.packages
@ -73,7 +73,7 @@ list of package
## environment\.fortify\.apps\.\<name>\.args ## environment\.fortify\.apps\.\*\.args
@ -92,7 +92,7 @@ null or (list of string)
## environment\.fortify\.apps\.\<name>\.capability\.dbus ## environment\.fortify\.apps\.\*\.capability\.dbus
@ -110,7 +110,7 @@ boolean
## environment\.fortify\.apps\.\<name>\.capability\.pulse ## environment\.fortify\.apps\.\*\.capability\.pulse
@ -128,7 +128,7 @@ boolean
## environment\.fortify\.apps\.\<name>\.capability\.wayland ## environment\.fortify\.apps\.\*\.capability\.wayland
@ -146,7 +146,7 @@ boolean
## environment\.fortify\.apps\.\<name>\.capability\.x11 ## environment\.fortify\.apps\.\*\.capability\.x11
@ -164,7 +164,7 @@ boolean
## environment\.fortify\.apps\.\<name>\.command ## environment\.fortify\.apps\.\*\.command
@ -184,7 +184,7 @@ null or string
## environment\.fortify\.apps\.\<name>\.dbus\.session ## environment\.fortify\.apps\.\*\.dbus\.session
@ -203,7 +203,7 @@ null or (function that evaluates to a(n) anything)
## environment\.fortify\.apps\.\<name>\.dbus\.system ## environment\.fortify\.apps\.\*\.dbus\.system
@ -222,7 +222,7 @@ null or anything
## environment\.fortify\.apps\.\<name>\.devel ## environment\.fortify\.apps\.\*\.devel
@ -245,7 +245,7 @@ boolean
## environment\.fortify\.apps\.\<name>\.device ## environment\.fortify\.apps\.\*\.device
@ -268,7 +268,7 @@ boolean
## environment\.fortify\.apps\.\<name>\.env ## environment\.fortify\.apps\.\*\.env
@ -286,7 +286,7 @@ null or (attribute set of string)
## environment\.fortify\.apps\.\<name>\.extraConfig ## environment\.fortify\.apps\.\*\.extraConfig
@ -304,16 +304,16 @@ anything
## environment\.fortify\.apps\.\<name>\.extraPaths ## environment\.fortify\.apps\.\*\.extraPaths
Extra paths to make available to the container\. Extra paths to make available to the sandbox\.
*Type:* *Type:*
list of (submodule) list of anything
@ -322,107 +322,7 @@ list of (submodule)
## environment\.fortify\.apps\.\<name>\.extraPaths\.\*\.dev ## environment\.fortify\.apps\.\*\.gpu
Whether to enable use of device files\.
*Type:*
boolean
*Default:*
` false `
*Example:*
` true `
## environment\.fortify\.apps\.\<name>\.extraPaths\.\*\.dst
Mount point in container, same as src if null\.
*Type:*
null or string
*Default:*
` null `
## environment\.fortify\.apps\.\<name>\.extraPaths\.\*\.require
Whether to enable start failure if the bind mount cannot be established for any reason\.
*Type:*
boolean
*Default:*
` false `
*Example:*
` true `
## environment\.fortify\.apps\.\<name>\.extraPaths\.\*\.src
Host filesystem path to make available to the container\.
*Type:*
string
## environment\.fortify\.apps\.\<name>\.extraPaths\.\*\.write
Whether to enable mounting path as writable\.
*Type:*
boolean
*Default:*
` false `
*Example:*
` true `
## environment\.fortify\.apps\.\<name>\.gpu
@ -441,7 +341,7 @@ null or boolean
## environment\.fortify\.apps\.\<name>\.groups ## environment\.fortify\.apps\.\*\.groups
@ -459,20 +359,25 @@ list of string
## environment\.fortify\.apps\.\<name>\.identity ## environment\.fortify\.apps\.\*\.id
Application identity\. Identity 0 is reserved for system services\. Freedesktop application ID\.
*Type:* *Type:*
integer between 1 and 9999 (both inclusive) null or string
## environment\.fortify\.apps\.\<name>\.insecureWayland *Default:*
` null `
## environment\.fortify\.apps\.\*\.insecureWayland
@ -495,7 +400,7 @@ boolean
## environment\.fortify\.apps\.\<name>\.mapRealUid ## environment\.fortify\.apps\.\*\.mapRealUid
@ -518,7 +423,7 @@ boolean
## environment\.fortify\.apps\.\<name>\.multiarch ## environment\.fortify\.apps\.\*\.multiarch
@ -541,7 +446,7 @@ boolean
## environment\.fortify\.apps\.\<name>\.name ## environment\.fortify\.apps\.\*\.name
@ -554,7 +459,7 @@ string
## environment\.fortify\.apps\.\<name>\.net ## environment\.fortify\.apps\.\*\.net
@ -577,7 +482,7 @@ boolean
## environment\.fortify\.apps\.\<name>\.nix ## environment\.fortify\.apps\.\*\.nix
@ -600,7 +505,7 @@ boolean
## environment\.fortify\.apps\.\<name>\.path ## environment\.fortify\.apps\.\*\.path
@ -619,7 +524,7 @@ null or string
## environment\.fortify\.apps\.\<name>\.script ## environment\.fortify\.apps\.\*\.script
@ -637,7 +542,7 @@ null or string
## environment\.fortify\.apps\.\<name>\.share ## environment\.fortify\.apps\.\*\.share
@ -656,30 +561,7 @@ null or package
## environment\.fortify\.apps\.\<name>\.shareUid ## environment\.fortify\.apps\.\*\.tty
Whether to enable sharing identity with another application\.
*Type:*
boolean
*Default:*
` false `
*Example:*
` true `
## environment\.fortify\.apps\.\<name>\.tty
@ -702,30 +584,7 @@ boolean
## environment\.fortify\.apps\.\<name>\.useCommonPaths ## environment\.fortify\.apps\.\*\.userns
Whether to enable common extra paths\.
*Type:*
boolean
*Default:*
` true `
*Example:*
` true `
## environment\.fortify\.apps\.\<name>\.userns
@ -748,7 +607,7 @@ boolean
## environment\.fortify\.apps\.\<name>\.verbose ## environment\.fortify\.apps\.\*\.verbose
@ -771,137 +630,6 @@ boolean
## environment\.fortify\.commonPaths
Common extra paths to make available to the container\.
*Type:*
list of (submodule)
*Default:*
` [ ] `
## environment\.fortify\.commonPaths\.\*\.dev
Whether to enable use of device files\.
*Type:*
boolean
*Default:*
` false `
*Example:*
` true `
## environment\.fortify\.commonPaths\.\*\.dst
Mount point in container, same as src if null\.
*Type:*
null or string
*Default:*
` null `
## environment\.fortify\.commonPaths\.\*\.require
Whether to enable start failure if the bind mount cannot be established for any reason\.
*Type:*
boolean
*Default:*
` false `
*Example:*
` true `
## environment\.fortify\.commonPaths\.\*\.src
Host filesystem path to make available to the container\.
*Type:*
string
## environment\.fortify\.commonPaths\.\*\.write
Whether to enable mounting path as writable\.
*Type:*
boolean
*Default:*
` false `
*Example:*
` true `
## environment\.fortify\.extraHomeConfig
Extra home-manager configuration to merge with all target users\.
*Type:*
anything
## environment\.fortify\.fsuPackage ## environment\.fortify\.fsuPackage
@ -916,7 +644,20 @@ package
*Default:* *Default:*
` <derivation fortify-fsu-0.4.1> ` ` <derivation fortify-fsu-0.4.0> `
## environment\.fortify\.home-manager
Target user shared home-manager configuration\.
*Type:*
function that evaluates to a(n) function that evaluates to a(n) attribute set of anything

View File

@ -3,38 +3,6 @@ packages:
let let
inherit (lib) types mkOption mkEnableOption; inherit (lib) types mkOption mkEnableOption;
mountPoint =
let
inherit (types)
str
submodule
nullOr
listOf
;
in
listOf (submodule {
options = {
dst = mkOption {
type = nullOr str;
default = null;
description = ''
Mount point in container, same as src if null.
'';
};
src = mkOption {
type = str;
description = ''
Host filesystem path to make available to the container.
'';
};
write = mkEnableOption "mounting path as writable";
dev = mkEnableOption "use of device files";
require = mkEnableOption "start failure if the bind mount cannot be established for any reason";
};
});
in in
{ {
@ -65,10 +33,14 @@ in
''; '';
}; };
extraHomeConfig = mkOption { home-manager = mkOption {
type = types.anything; type =
let
inherit (types) functionTo attrsOf anything;
in
functionTo (functionTo (attrsOf anything));
description = '' description = ''
Extra home-manager configuration to merge with all target users. Target user shared home-manager configuration.
''; '';
}; };
@ -76,7 +48,6 @@ in
type = type =
let let
inherit (types) inherit (types)
ints
str str
bool bool
package package
@ -88,7 +59,7 @@ in
functionTo functionTo
; ;
in in
attrsOf (submodule { listOf (submodule {
options = { options = {
name = mkOption { name = mkOption {
type = str; type = str;
@ -99,13 +70,13 @@ in
verbose = mkEnableOption "launchers with verbose output"; verbose = mkEnableOption "launchers with verbose output";
identity = mkOption { id = mkOption {
type = ints.between 1 9999; type = nullOr str;
default = null;
description = '' description = ''
Application identity. Identity 0 is reserved for system services. Freedesktop application ID.
''; '';
}; };
shareUid = mkEnableOption "sharing identity with another application";
packages = mkOption { packages = mkOption {
type = listOf package; type = listOf package;
@ -218,15 +189,11 @@ in
''; '';
}; };
useCommonPaths = mkEnableOption "common extra paths" // {
default = true;
};
extraPaths = mkOption { extraPaths = mkOption {
type = mountPoint; type = listOf anything;
default = [ ]; default = [ ];
description = '' description = ''
Extra paths to make available to the container. Extra paths to make available to the sandbox.
''; '';
}; };
@ -274,18 +241,8 @@ in
}; };
}; };
}); });
default = { };
description = ''
Declaratively configured fortify apps.
'';
};
commonPaths = mkOption {
type = mountPoint;
default = [ ]; default = [ ];
description = '' description = "Declarative fortify apps.";
Common extra paths to make available to the container.
'';
}; };
stateDir = mkOption { stateDir = mkOption {

View File

@ -31,7 +31,7 @@
buildGoModule rec { buildGoModule rec {
pname = "fortify"; pname = "fortify";
version = "0.4.1"; version = "0.4.0";
src = builtins.path { src = builtins.path {
name = "${pname}-src"; name = "${pname}-src";

View File

@ -7,7 +7,6 @@ import (
"log" "log"
"strings" "strings"
"sync" "sync"
"syscall"
"git.gensokyo.uk/security/fortify/dbus" "git.gensokyo.uk/security/fortify/dbus"
) )
@ -27,62 +26,64 @@ func (sys *I) MustProxyDBus(sessionPath string, session *dbus.Config, systemPath
func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath string) (func(), error) { func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath string) (func(), error) {
d := new(DBus) d := new(DBus)
// session bus is required as otherwise this is effectively a very expensive noop // session bus is mandatory
if session == nil { if session == nil {
return nil, msg.WrapErr(ErrDBusConfig, return nil, msg.WrapErr(ErrDBusConfig,
"attempted to create message bus proxy args without session bus config") "attempted to seal message bus proxy without session bus config")
} }
// system bus is optional // system bus is optional
d.system = system != nil d.system = system != nil
d.sessionBus[0], d.systemBus[0] = dbus.Address() // upstream address, downstream socket path
d.sessionBus[1], d.systemBus[1] = sessionPath, systemPath var sessionBus, systemBus [2]string
d.out = &scanToFmsg{msg: new(strings.Builder)}
if final, err := dbus.Finalise(d.sessionBus, d.systemBus, session, system); err != nil { // resolve upstream bus addresses
if errors.Is(err, syscall.EINVAL) { sessionBus[0], systemBus[0] = dbus.Address()
return nil, msg.WrapErr(err, "message bus proxy configuration contains NUL byte")
} // set paths from caller
return nil, wrapErrSuffix(err, "cannot finalise message bus proxy:") sessionBus[1], systemBus[1] = sessionPath, systemPath
} else {
if msg.IsVerbose() { // create proxy instance
msg.Verbose("session bus proxy:", session.Args(d.sessionBus)) d.proxy = dbus.New(sessionBus, systemBus)
defer func() {
if msg.IsVerbose() && d.proxy.Sealed() {
msg.Verbose("sealed session proxy", session.Args(sessionBus))
if system != nil { if system != nil {
msg.Verbose("system bus proxy:", system.Args(d.systemBus)) msg.Verbose("sealed system proxy", system.Args(systemBus))
} }
msg.Verbose("message bus proxy final args:", d.proxy)
// this calls the argsWt String method
msg.Verbose("message bus proxy final args:", final.WriterTo)
}
d.final = final
} }
}()
// queue operation
sys.ops = append(sys.ops, d) sys.ops = append(sys.ops, d)
return d.out.Dump, nil
// seal dbus proxy
d.out = &scanToFmsg{msg: new(strings.Builder)}
return d.out.Dump, wrapErrSuffix(d.proxy.Seal(session, system),
"cannot seal message bus proxy:")
} }
type DBus struct { type DBus struct {
proxy *dbus.Proxy // populated during apply proxy *dbus.Proxy
final *dbus.Final
out *scanToFmsg out *scanToFmsg
// whether system bus proxy is enabled // whether system bus proxy is enabled
system bool system bool
sessionBus, systemBus dbus.ProxyPair
} }
func (d *DBus) Type() Enablement { return Process } func (d *DBus) Type() Enablement { return Process }
func (d *DBus) apply(sys *I) error { func (d *DBus) apply(sys *I) error {
msg.Verbosef("session bus proxy on %q for upstream %q", d.sessionBus[1], d.sessionBus[0]) msg.Verbosef("session bus proxy on %q for upstream %q", d.proxy.Session()[1], d.proxy.Session()[0])
if d.system { if d.system {
msg.Verbosef("system bus proxy on %q for upstream %q", d.systemBus[1], d.systemBus[0]) msg.Verbosef("system bus proxy on %q for upstream %q", d.proxy.System()[1], d.proxy.System()[0])
} }
d.proxy = dbus.New(sys.ctx, d.final, d.out) // this starts the process and blocks until ready
if err := d.proxy.Start(); err != nil { if err := d.proxy.Start(sys.ctx, d.out, true); err != nil {
d.out.Dump() d.out.Dump()
return wrapErrSuffix(err, return wrapErrSuffix(err,
"cannot start message bus proxy:") "cannot start message bus proxy:")

View File

@ -30,9 +30,13 @@
environment = { environment = {
systemPackages = with pkgs; [ systemPackages = with pkgs; [
# For glinfo and wayland-info:
mesa-demos
wayland-utils
# For D-Bus tests: # For D-Bus tests:
mako
libnotify libnotify
mako
]; ];
variables = { variables = {
@ -95,51 +99,33 @@
stateDir = "/var/lib/fortify"; stateDir = "/var/lib/fortify";
users.alice = 0; users.alice = 0;
extraHomeConfig = { home-manager = _: _: { home.stateVersion = "23.05"; };
home.stateVersion = "23.05";
};
apps = { apps = [
"cat.gensokyo.extern.foot.noEnablements" = { {
name = "ne-foot"; name = "ne-foot";
identity = 1;
verbose = true; verbose = true;
share = pkgs.foot; share = pkgs.foot;
packages = with pkgs; [ packages = [ pkgs.foot ];
foot
# For wayland-info:
wayland-utils
];
command = "foot"; command = "foot";
capability = { capability = {
dbus = false; dbus = false;
pulse = false; pulse = false;
}; };
}; }
{
"cat.gensokyo.extern.foot.pulseaudio" = {
name = "pa-foot"; name = "pa-foot";
identity = 2;
verbose = true; verbose = true;
share = pkgs.foot; share = pkgs.foot;
packages = [ pkgs.foot ]; packages = [ pkgs.foot ];
command = "foot"; command = "foot";
capability.dbus = false; capability.dbus = false;
}; }
{
"cat.gensokyo.extern.Alacritty.x11" = {
name = "x11-alacritty"; name = "x11-alacritty";
identity = 3;
verbose = true; verbose = true;
share = pkgs.alacritty; share = pkgs.alacritty;
packages = with pkgs; [ packages = [ pkgs.alacritty ];
# For X11 terminal emulator:
alacritty
# For glinfo:
mesa-demos
];
command = "alacritty"; command = "alacritty";
capability = { capability = {
wayland = false; wayland = false;
@ -147,30 +133,21 @@
dbus = false; dbus = false;
pulse = false; pulse = false;
}; };
}; }
{
"cat.gensokyo.extern.foot.directWayland" = {
name = "da-foot"; name = "da-foot";
identity = 4;
verbose = true; verbose = true;
insecureWayland = true; insecureWayland = true;
share = pkgs.foot; share = pkgs.foot;
packages = with pkgs; [ packages = [ pkgs.foot ];
foot
# For wayland-info:
wayland-utils
];
command = "foot"; command = "foot";
capability = { capability = {
dbus = false; dbus = false;
pulse = false; pulse = false;
}; };
}; }
{
"cat.gensokyo.extern.strace.wantFail" = {
name = "strace-failure"; name = "strace-failure";
identity = 5;
verbose = true; verbose = true;
share = pkgs.strace; share = pkgs.strace;
command = "strace true"; command = "strace true";
@ -180,7 +157,7 @@
dbus = false; dbus = false;
pulse = false; pulse = false;
}; };
}; }
}; ];
}; };
} }

View File

@ -24,7 +24,7 @@ let
}; };
callTestCase = callTestCase =
path: identity: path:
let let
tc = import path { tc = import path {
inherit inherit
@ -36,14 +36,8 @@ let
in in
{ {
name = "check-sandbox-${tc.name}"; name = "check-sandbox-${tc.name}";
inherit identity;
verbose = true; verbose = true;
inherit (tc) inherit (tc) tty device mapRealUid;
tty
device
mapRealUid
useCommonPaths
;
share = testProgram; share = testProgram;
packages = [ ]; packages = [ ];
path = "${testProgram}/bin/fortify-test"; path = "${testProgram}/bin/fortify-test";
@ -52,12 +46,10 @@ let
(toString (builtins.toFile "fortify-${tc.name}-want.json" (builtins.toJSON tc.want))) (toString (builtins.toFile "fortify-${tc.name}-want.json" (builtins.toJSON tc.want)))
]; ];
}; };
testCaseName = name: "cat.gensokyo.fortify.test." + name;
in in
{ {
${testCaseName "preset"} = callTestCase ./preset.nix 1; preset = callTestCase ./preset.nix;
${testCaseName "tty"} = callTestCase ./tty.nix 2; tty = callTestCase ./tty.nix;
${testCaseName "mapuid"} = callTestCase ./mapuid.nix 3; mapuid = callTestCase ./mapuid.nix;
${testCaseName "device"} = callTestCase ./device.nix 4; device = callTestCase ./device.nix;
} }

View File

@ -8,7 +8,6 @@
tty = false; tty = false;
device = true; device = true;
mapRealUid = false; mapRealUid = false;
useCommonPaths = true;
want = { want = {
env = [ env = [
@ -141,12 +140,7 @@
u0 = fs "800001c0" { u0 = fs "800001c0" {
a4 = fs "800001c0" { a4 = fs "800001c0" {
".cache" = fs "800001ed" { ".keep" = fs "80001ff" null ""; } null; ".cache" = fs "800001ed" { ".keep" = fs "80001ff" null ""; } null;
".config" = fs "800001ed" { ".config" = fs "800001ed" { "environment.d" = fs "800001ed" { "10-home-manager.conf" = fs "80001ff" null null; } null; } null;
"environment.d" = fs "800001ed" { "10-home-manager.conf" = fs "80001ff" null null; } null;
systemd = fs "800001ed" {
user = fs "800001ed" { "tray.target" = fs "80001ff" null null; } null;
} null;
} null;
".local" = fs "800001ed" { ".local" = fs "800001ed" {
state = fs "800001ed" { state = fs "800001ed" {
home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null; home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null;
@ -170,7 +164,6 @@
} null; } null;
} null; } null;
run = fs "800001ed" { nscd = fs "800001ed" { } null; } null; run = fs "800001ed" { nscd = fs "800001ed" { } null; } null;
cache = fs "800001ed" { private = fs "800001c0" null null; } null;
} null; } null;
} null; } null;
@ -192,7 +185,6 @@
(ent "/dev" "/sys/dev" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw") (ent "/dev" "/sys/dev" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
(ent "/devices" "/sys/devices" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw") (ent "/devices" "/sys/devices" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
(ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/var/cache" "/var/cache" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000004,gid=1000004") (ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000004,gid=1000004")
(ent "/" "/run/user/65534" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8192k,mode=700,uid=1000004,gid=1000004") (ent "/" "/run/user/65534" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8192k,mode=700,uid=1000004,gid=1000004")

View File

@ -8,7 +8,6 @@
tty = false; tty = false;
device = false; device = false;
mapRealUid = true; mapRealUid = true;
useCommonPaths = true;
want = { want = {
env = [ env = [
@ -165,12 +164,7 @@
u0 = fs "800001c0" { u0 = fs "800001c0" {
a3 = fs "800001c0" { a3 = fs "800001c0" {
".cache" = fs "800001ed" { ".keep" = fs "80001ff" null ""; } null; ".cache" = fs "800001ed" { ".keep" = fs "80001ff" null ""; } null;
".config" = fs "800001ed" { ".config" = fs "800001ed" { "environment.d" = fs "800001ed" { "10-home-manager.conf" = fs "80001ff" null null; } null; } null;
"environment.d" = fs "800001ed" { "10-home-manager.conf" = fs "80001ff" null null; } null;
systemd = fs "800001ed" {
user = fs "800001ed" { "tray.target" = fs "80001ff" null null; } null;
} null;
} null;
".local" = fs "800001ed" { ".local" = fs "800001ed" {
state = fs "800001ed" { state = fs "800001ed" {
home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null; home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null;
@ -194,7 +188,6 @@
} null; } null;
} null; } null;
run = fs "800001ed" { nscd = fs "800001ed" { } null; } null; run = fs "800001ed" { nscd = fs "800001ed" { } null; } null;
cache = fs "800001ed" { private = fs "800001c0" null null; } null;
} null; } null;
} null; } null;
@ -220,7 +213,6 @@
(ent "/dev" "/sys/dev" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw") (ent "/dev" "/sys/dev" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
(ent "/devices" "/sys/devices" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw") (ent "/devices" "/sys/devices" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
(ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/var/cache" "/var/cache" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000003,gid=1000003") (ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000003,gid=1000003")
(ent "/" "/run/user/1000" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8192k,mode=700,uid=1000003,gid=1000003") (ent "/" "/run/user/1000" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8192k,mode=700,uid=1000003,gid=1000003")

View File

@ -8,7 +8,6 @@
tty = false; tty = false;
device = false; device = false;
mapRealUid = false; mapRealUid = false;
useCommonPaths = false;
want = { want = {
env = [ env = [
@ -165,12 +164,7 @@
u0 = fs "800001c0" { u0 = fs "800001c0" {
a1 = fs "800001c0" { a1 = fs "800001c0" {
".cache" = fs "800001ed" { ".keep" = fs "80001ff" null ""; } null; ".cache" = fs "800001ed" { ".keep" = fs "80001ff" null ""; } null;
".config" = fs "800001ed" { ".config" = fs "800001ed" { "environment.d" = fs "800001ed" { "10-home-manager.conf" = fs "80001ff" null null; } null; } null;
"environment.d" = fs "800001ed" { "10-home-manager.conf" = fs "80001ff" null null; } null;
systemd = fs "800001ed" {
user = fs "800001ed" { "tray.target" = fs "80001ff" null null; } null;
} null;
} null;
".local" = fs "800001ed" { ".local" = fs "800001ed" {
state = fs "800001ed" { state = fs "800001ed" {
home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null; home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null;

View File

@ -8,7 +8,6 @@
tty = true; tty = true;
device = false; device = false;
mapRealUid = false; mapRealUid = false;
useCommonPaths = true;
want = { want = {
env = [ env = [
@ -166,12 +165,7 @@
u0 = fs "800001c0" { u0 = fs "800001c0" {
a2 = fs "800001c0" { a2 = fs "800001c0" {
".cache" = fs "800001ed" { ".keep" = fs "80001ff" null ""; } null; ".cache" = fs "800001ed" { ".keep" = fs "80001ff" null ""; } null;
".config" = fs "800001ed" { ".config" = fs "800001ed" { "environment.d" = fs "800001ed" { "10-home-manager.conf" = fs "80001ff" null null; } null; } null;
"environment.d" = fs "800001ed" { "10-home-manager.conf" = fs "80001ff" null null; } null;
systemd = fs "800001ed" {
user = fs "800001ed" { "tray.target" = fs "80001ff" null null; } null;
} null;
} null;
".local" = fs "800001ed" { ".local" = fs "800001ed" {
state = fs "800001ed" { state = fs "800001ed" {
home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null; home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null;
@ -195,7 +189,6 @@
} null; } null;
} null; } null;
run = fs "800001ed" { nscd = fs "800001ed" { } null; } null; run = fs "800001ed" { nscd = fs "800001ed" { } null; } null;
cache = fs "800001ed" { private = fs "800001c0" null null; } null;
} null; } null;
} null; } null;
@ -222,7 +215,6 @@
(ent "/dev" "/sys/dev" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw") (ent "/dev" "/sys/dev" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
(ent "/devices" "/sys/devices" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw") (ent "/devices" "/sys/devices" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
(ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) (ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
(ent "/var/cache" "/var/cache" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000002,gid=1000002") (ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000002,gid=1000002")
(ent "/" "/run/user/65534" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8192k,mode=700,uid=1000002,gid=1000002") (ent "/" "/run/user/65534" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8192k,mode=700,uid=1000002,gid=1000002")

View File

@ -6,6 +6,7 @@
}: }:
let let
testProgram = pkgs.callPackage ./tool/package.nix { inherit (config.environment.fortify.package) version; }; testProgram = pkgs.callPackage ./tool/package.nix { inherit (config.environment.fortify.package) version; };
testCases = import ./case lib testProgram;
in in
{ {
users.users = { users.users = {
@ -64,17 +65,13 @@ in
stateDir = "/var/lib/fortify"; stateDir = "/var/lib/fortify";
users.alice = 0; users.alice = 0;
extraHomeConfig = { home-manager = _: _: { home.stateVersion = "23.05"; };
home.stateVersion = "23.05";
};
commonPaths = [ apps = with testCases; [
{ preset
src = "/var/cache"; tty
write = true; mapuid
} device
]; ];
apps = import ./case lib testProgram;
}; };
} }

View File

@ -152,8 +152,6 @@ print(machine.fail("sudo -u alice -i fortify -v run --wayland true"))
fortify('-v run --wayland --dbus notify-send -a "NixOS Tests" "Test notification" "Notification from within sandbox." && touch /tmp/dbus-ok') fortify('-v run --wayland --dbus notify-send -a "NixOS Tests" "Test notification" "Notification from within sandbox." && touch /tmp/dbus-ok')
machine.wait_for_file("/tmp/dbus-ok", timeout=15) machine.wait_for_file("/tmp/dbus-ok", timeout=15)
collect_state_ui("dbus_notify_exited") collect_state_ui("dbus_notify_exited")
# not in pid namespace, verify termination
machine.wait_until_fails("pgrep xdg-dbus-proxy")
machine.succeed("pkill -9 mako") machine.succeed("pkill -9 mako")
# Check revert type selection: # Check revert type selection: