Compare commits

..

No commits in common. "cdda33555cf5967221523b90ede1f4eda2a82ce3" and "064db9f02088448b42758dc089787ca7d05b1510" have entirely different histories.

22 changed files with 79 additions and 591 deletions

View File

@ -21,8 +21,8 @@ jobs:
- name: Get dependencies - name: Get dependencies
run: >- run: >-
apt-get update && sudo apt-get update &&
apt-get install -y sudo apt-get install -y
gcc gcc
pkg-config pkg-config
libacl1-dev libacl1-dev

View File

@ -20,8 +20,8 @@ jobs:
- name: Get dependencies - name: Get dependencies
run: >- run: >-
apt-get update && sudo apt-get update &&
apt-get install -y sudo apt-get install -y
gcc gcc
pkg-config pkg-config
libacl1-dev libacl1-dev

View File

@ -3,7 +3,7 @@ Fortify
[![Go Reference](https://pkg.go.dev/badge/git.ophivana.moe/cat/fortify.svg)](https://pkg.go.dev/git.ophivana.moe/cat/fortify) [![Go Reference](https://pkg.go.dev/badge/git.ophivana.moe/cat/fortify.svg)](https://pkg.go.dev/git.ophivana.moe/cat/fortify)
Lets you run graphical applications as another user in a confined environment with a nice NixOS Lets you run graphical applications as another user ~~in an Android-like sandbox environment~~ (WIP) with a nice NixOS
module to configure target users and provide launchers and desktop files for your privileged user. module to configure target users and provide launchers and desktop files for your privileged user.
Why would you want this? Why would you want this?
@ -12,7 +12,7 @@ Why would you want this?
- It protects applications from each other. - It protects applications from each other.
- It provides UID isolation on top of the standard application sandbox. - It provides UID isolation on top of ~~the standard application sandbox~~ (WIP).
There are a few different things to set up for this to work: There are a few different things to set up for this to work:

View File

@ -34,12 +34,6 @@
devShells = forAllSystems (system: { devShells = forAllSystems (system: {
default = nixpkgsFor.${system}.mkShell { default = nixpkgsFor.${system}.mkShell {
buildInputs =
with nixpkgsFor.${system};
self.packages.${system}.fortify.buildInputs;
};
withPackage = nixpkgsFor.${system}.mkShell {
buildInputs = buildInputs =
with nixpkgsFor.${system}; with nixpkgsFor.${system};
self.packages.${system}.fortify.buildInputs ++ [ self.packages.${system}.fortify ]; self.packages.${system}.fortify.buildInputs ++ [ self.packages.${system}.fortify ];

View File

@ -7,39 +7,26 @@ import (
) )
type App interface { type App interface {
// ID returns a copy of App's unique ID.
ID() ID
// Start sets up the system and starts the App.
Start() error
// Wait waits for App's process to exit and reverts system setup.
Wait() (int, error)
// WaitErr returns error returned by the underlying wait syscall.
WaitErr() error
Seal(config *Config) error Seal(config *Config) error
Start() error
Wait() (int, error)
WaitErr() error
String() string String() string
} }
type app struct { type app struct {
// application unique identifier
id *ID
// underlying user switcher process
cmd *exec.Cmd
// child process related information // child process related information
seal *appSeal seal *appSeal
// underlying fortified child process
cmd *exec.Cmd
// wayland connection if wayland mediation is enabled // wayland connection if wayland mediation is enabled
wayland *net.UnixConn wayland *net.UnixConn
// error returned waiting for process // error returned waiting for process
waitErr error wait error
lock sync.RWMutex lock sync.RWMutex
} }
func (a *app) ID() ID {
return *a.id
}
func (a *app) String() string { func (a *app) String() string {
if a == nil { if a == nil {
return "(invalid fortified app)" return "(invalid fortified app)"
@ -60,11 +47,9 @@ func (a *app) String() string {
} }
func (a *app) WaitErr() error { func (a *app) WaitErr() error {
return a.waitErr return a.wait
} }
func New() (App, error) { func New() App {
a := new(app) return new(app)
a.id = new(ID)
return a, newAppID(a.id)
} }

View File

@ -5,13 +5,14 @@ import (
"encoding/hex" "encoding/hex"
) )
type ID [16]byte type appID [16]byte
func (a *ID) String() string { func (a *appID) String() string {
return hex.EncodeToString(a[:]) return hex.EncodeToString(a[:])
} }
func newAppID(id *ID) error { func newAppID() (*appID, error) {
_, err := rand.Read(id[:]) a := &appID{}
return err _, err := rand.Read(a[:])
return a, err
} }

View File

@ -21,11 +21,6 @@ const (
LaunchMethodMachineCtl LaunchMethodMachineCtl
) )
var method = [...]string{
LaunchMethodSudo: "sudo",
LaunchMethodMachineCtl: "systemd",
}
var ( var (
ErrConfig = errors.New("no configuration to seal") ErrConfig = errors.New("no configuration to seal")
ErrUser = errors.New("unknown user") ErrUser = errors.New("unknown user")
@ -53,17 +48,24 @@ func (a *app) Seal(config *Config) error {
// create seal // create seal
seal := new(appSeal) seal := new(appSeal)
// generate application ID
if id, err := newAppID(); err != nil {
return fmsg.WrapErrorSuffix(err,
"cannot generate application ID:")
} else {
seal.id = id
}
// fetch system constants // fetch system constants
seal.SystemConstants = internal.GetSC() seal.SystemConstants = internal.GetSC()
// pass through config values // pass through config values
seal.id = a.id.String()
seal.fid = config.ID seal.fid = config.ID
seal.command = config.Command seal.command = config.Command
// parses launch method text and looks up tool path // parses launch method text and looks up tool path
switch config.Method { switch config.Method {
case method[LaunchMethodSudo]: case "sudo":
seal.launchOption = LaunchMethodSudo seal.launchOption = LaunchMethodSudo
if sudoPath, err := exec.LookPath("sudo"); err != nil { if sudoPath, err := exec.LookPath("sudo"); err != nil {
return fmsg.WrapError(ErrSudo, return fmsg.WrapError(ErrSudo,
@ -71,7 +73,7 @@ func (a *app) Seal(config *Config) error {
} else { } else {
seal.toolPath = sudoPath seal.toolPath = sudoPath
} }
case method[LaunchMethodMachineCtl]: case "systemd":
seal.launchOption = LaunchMethodMachineCtl seal.launchOption = LaunchMethodMachineCtl
if !internal.SdBootedV { if !internal.SdBootedV {
return fmsg.WrapError(ErrSystemd, return fmsg.WrapError(ErrSystemd,

View File

@ -31,8 +31,17 @@ func (seal *appSeal) shareRuntime() {
// ensure runtime directory ACL (e.g. `/run/user/%d`) // ensure runtime directory ACL (e.g. `/run/user/%d`)
seal.sys.UpdatePermType(system.User, seal.RuntimePath, acl.Execute) seal.sys.UpdatePermType(system.User, seal.RuntimePath, acl.Execute)
// ensure Share (e.g. `/tmp/fortify.%d`)
// acl is unnecessary as this directory is world executable
seal.sys.Ensure(seal.SharePath, 0701)
// ensure process-specific share (e.g. `/tmp/fortify.%d/%s`)
// acl is unnecessary as this directory is world executable
seal.share = path.Join(seal.SharePath, seal.id.String())
seal.sys.Ephemeral(system.Process, seal.share, 0701)
// ensure process-specific share local to XDG_RUNTIME_DIR (e.g. `/run/user/%d/fortify/%s`) // ensure process-specific share local to XDG_RUNTIME_DIR (e.g. `/run/user/%d/fortify/%s`)
seal.shareLocal = path.Join(seal.RunDirPath, seal.id) seal.shareLocal = path.Join(seal.RunDirPath, seal.id.String())
seal.sys.Ephemeral(system.Process, seal.shareLocal, 0700) seal.sys.Ephemeral(system.Process, seal.shareLocal, 0700)
seal.sys.UpdatePerm(seal.shareLocal, acl.Execute) seal.sys.UpdatePerm(seal.shareLocal, acl.Execute)
} }

View File

@ -14,31 +14,6 @@ const (
// shareSystem queues various system-related actions // shareSystem queues various system-related actions
func (seal *appSeal) shareSystem() { func (seal *appSeal) shareSystem() {
// ensure Share (e.g. `/tmp/fortify.%d`)
// acl is unnecessary as this directory is world executable
seal.sys.Ensure(seal.SharePath, 0701)
// ensure process-specific share (e.g. `/tmp/fortify.%d/%s`)
// acl is unnecessary as this directory is world executable
seal.share = path.Join(seal.SharePath, seal.id)
seal.sys.Ephemeral(system.Process, seal.share, 0701)
// ensure child tmpdir parent directory (e.g. `/tmp/fortify.%d/tmpdir`)
targetTmpdirParent := path.Join(seal.SharePath, "tmpdir")
seal.sys.Ensure(targetTmpdirParent, 0700)
seal.sys.UpdatePermType(system.User, targetTmpdirParent, acl.Execute)
// ensure child tmpdir (e.g. `/tmp/fortify.%d/tmpdir/%d`)
targetTmpdir := path.Join(targetTmpdirParent, seal.sys.user.Uid)
seal.sys.Ensure(targetTmpdir, 01700)
seal.sys.UpdatePermType(system.User, targetTmpdir, acl.Read, acl.Write, acl.Execute)
seal.sys.bwrap.Bind(targetTmpdir, "/tmp", false, true)
// mount tmpfs on inner shared directory (e.g. `/tmp/fortify.%d`)
seal.sys.bwrap.Tmpfs(seal.SharePath, 1*1024*1024)
}
func (seal *appSeal) sharePasswd() {
// look up shell // look up shell
sh := "/bin/sh" sh := "/bin/sh"
if s, ok := os.LookupEnv(shell); ok { if s, ok := os.LookupEnv(shell); ok {
@ -69,3 +44,21 @@ func (seal *appSeal) sharePasswd() {
seal.sys.bwrap.Bind(passwdPath, "/etc/passwd") seal.sys.bwrap.Bind(passwdPath, "/etc/passwd")
seal.sys.bwrap.Bind(groupPath, "/etc/group") seal.sys.bwrap.Bind(groupPath, "/etc/group")
} }
func (seal *appSeal) shareTmpdirChild() string {
// ensure child tmpdir parent directory (e.g. `/tmp/fortify.%d/tmpdir`)
targetTmpdirParent := path.Join(seal.SharePath, "tmpdir")
seal.sys.Ensure(targetTmpdirParent, 0700)
seal.sys.UpdatePermType(system.User, targetTmpdirParent, acl.Execute)
// ensure child tmpdir (e.g. `/tmp/fortify.%d/tmpdir/%d`)
targetTmpdir := path.Join(targetTmpdirParent, seal.sys.user.Uid)
seal.sys.Ensure(targetTmpdir, 01700)
seal.sys.UpdatePermType(system.User, targetTmpdir, acl.Read, acl.Write, acl.Execute)
seal.sys.bwrap.Bind(targetTmpdir, "/tmp", false, true)
// mount tmpfs on inner shared directory (e.g. `/tmp/fortify.%d`)
seal.sys.bwrap.Tmpfs(seal.SharePath, 1*1024*1024)
return targetTmpdir
}

View File

@ -94,7 +94,7 @@ func (a *app) Start() error {
PID: a.cmd.Process.Pid, PID: a.cmd.Process.Pid,
Command: a.seal.command, Command: a.seal.command,
Capability: a.seal.et, Capability: a.seal.et,
Method: method[a.seal.launchOption], Launcher: a.seal.toolPath,
Argv: a.cmd.Args, Argv: a.cmd.Args,
Time: startTime, Time: startTime,
} }
@ -173,7 +173,7 @@ func (a *app) Wait() (int, error) {
var exitError *exec.ExitError var exitError *exec.ExitError
if !errors.As(err, &exitError) { if !errors.As(err, &exitError) {
// should be unreachable // should be unreachable
a.waitErr = err a.wait = err
} }
// store non-zero return code // store non-zero return code

View File

@ -8,18 +8,19 @@ import (
"git.ophivana.moe/cat/fortify/internal" "git.ophivana.moe/cat/fortify/internal"
"git.ophivana.moe/cat/fortify/internal/state" "git.ophivana.moe/cat/fortify/internal/state"
"git.ophivana.moe/cat/fortify/internal/system" "git.ophivana.moe/cat/fortify/internal/system"
"git.ophivana.moe/cat/fortify/internal/verbose"
) )
// appSeal seals the application with child-related information // appSeal seals the application with child-related information
type appSeal struct { type appSeal struct {
// application unique identifier
id *appID
// wayland socket path if mediated wayland is enabled // wayland socket path if mediated wayland is enabled
wl string wl string
// wait for wayland client to exit if mediated wayland is enabled, // wait for wayland client to exit if mediated wayland is enabled,
// (wlDone == nil) determines whether mediated wayland setup is performed // (wlDone == nil) determines whether mediated wayland setup is performed
wlDone chan struct{} wlDone chan struct{}
// app unique ID string representation
id string
// freedesktop application ID // freedesktop application ID
fid string fid string
// argv to start process with in the final confined environment // argv to start process with in the final confined environment
@ -75,9 +76,10 @@ func (seal *appSeal) shareAll(bus [2]*dbus.Config) error {
} }
seal.shared = true seal.shared = true
seal.shareSystem() targetTmpdir := seal.shareTmpdirChild()
verbose.Printf("child tmpdir %q configured\n", targetTmpdir)
seal.shareRuntime() seal.shareRuntime()
seal.sharePasswd() seal.shareSystem()
if err := seal.shareDisplay(); err != nil { if err := seal.shareDisplay(); err != nil {
return err return err
} }

View File

@ -145,12 +145,11 @@ func doInit(fd uintptr) {
default: default:
r = 255 r = 255
} }
go func() {
time.Sleep(residualProcessTimeout)
close(timeout)
}()
} }
go func() {
time.Sleep(residualProcessTimeout)
close(timeout)
}()
case <-done: case <-done:
os.Exit(r) os.Exit(r)
case <-timeout: case <-timeout:

View File

@ -68,7 +68,7 @@ func (s *simpleStore) mustPrintLauncherState(w **tabwriter.Writer, now time.Time
// write header when initialising // write header when initialising
if !verbose.Get() { if !verbose.Get() {
_, _ = fmt.Fprintln(*w, "\tUID\tPID\tUptime\tEnablements\tMethod\tCommand") _, _ = fmt.Fprintln(*w, "\tUID\tPID\tUptime\tEnablements\tLauncher\tCommand")
} else { } else {
// argv is emitted in body when verbose // argv is emitted in body when verbose
_, _ = fmt.Fprintln(*w, "\tUID\tPID\tArgv") _, _ = fmt.Fprintln(*w, "\tUID\tPID\tArgv")
@ -98,7 +98,7 @@ func (s *simpleStore) mustPrintLauncherState(w **tabwriter.Writer, now time.Time
if !verbose.Get() { if !verbose.Get() {
_, _ = fmt.Fprintf(*w, "\t%s\t%d\t%s\t%s\t%s\t%s\n", _, _ = fmt.Fprintf(*w, "\t%s\t%d\t%s\t%s\t%s\t%s\n",
s.path[len(s.path)-1], state.PID, now.Sub(state.Time).Round(time.Second).String(), strings.TrimPrefix(ets.String(), ", "), state.Method, s.path[len(s.path)-1], state.PID, now.Sub(state.Time).Round(time.Second).String(), strings.TrimPrefix(ets.String(), ", "), state.Launcher,
state.Command) state.Command)
} else { } else {
// emit argv instead when verbose // emit argv instead when verbose

View File

@ -33,8 +33,8 @@ type State struct {
// capability enablements applied to child // capability enablements applied to child
Capability system.Enablements Capability system.Enablements
// user switch method // resolved launcher path
Method string Launcher string
// full argv whe launching // full argv whe launching
Argv []string Argv []string
// process start time // process start time

View File

@ -1,89 +0,0 @@
package system
import (
"testing"
"git.ophivana.moe/cat/fortify/acl"
)
func TestUpdatePerm(t *testing.T) {
testCases := []struct {
path string
perms []acl.Perm
}{
{"/run/user/1971/fortify", []acl.Perm{acl.Execute}},
{"/tmp/fortify.1971/tmpdir/150", []acl.Perm{acl.Read, acl.Write, acl.Execute}},
}
for _, tc := range testCases {
t.Run(tc.path+permSubTestSuffix(tc.perms), func(t *testing.T) {
sys := New(150)
sys.UpdatePerm(tc.path, tc.perms...)
(&tcOp{Process, tc.path}).test(t, sys.ops, []Op{&ACL{Process, tc.path, tc.perms}}, "UpdatePerm")
})
}
}
func TestUpdatePermType(t *testing.T) {
testCases := []struct {
perms []acl.Perm
tcOp
}{
{[]acl.Perm{acl.Execute}, tcOp{User, "/tmp/fortify.1971/tmpdir"}},
{[]acl.Perm{acl.Read, acl.Write, acl.Execute}, tcOp{User, "/tmp/fortify.1971/tmpdir/150"}},
{[]acl.Perm{acl.Execute}, tcOp{Process, "/run/user/1971/fortify/fcb8a12f7c482d183ade8288c3de78b5"}},
{[]acl.Perm{acl.Read}, tcOp{Process, "/tmp/fortify.1971/fcb8a12f7c482d183ade8288c3de78b5/passwd"}},
{[]acl.Perm{acl.Read}, tcOp{Process, "/tmp/fortify.1971/fcb8a12f7c482d183ade8288c3de78b5/group"}},
{[]acl.Perm{acl.Read, acl.Write, acl.Execute}, tcOp{EWayland, "/run/user/1971/wayland-0"}},
}
for _, tc := range testCases {
t.Run(tc.path+"_"+TypeString(tc.et)+permSubTestSuffix(tc.perms), func(t *testing.T) {
sys := New(150)
sys.UpdatePermType(tc.et, tc.path, tc.perms...)
tc.test(t, sys.ops, []Op{&ACL{tc.et, tc.path, tc.perms}}, "UpdatePermType")
})
}
}
func TestACL_String(t *testing.T) {
testCases := []struct {
want string
perms []acl.Perm
}{
{"---", []acl.Perm{}},
{"r--", []acl.Perm{acl.Read}},
{"-w-", []acl.Perm{acl.Write}},
{"--x", []acl.Perm{acl.Execute}},
{"rw-", []acl.Perm{acl.Read, acl.Write}},
{"r-x", []acl.Perm{acl.Read, acl.Execute}},
{"rwx", []acl.Perm{acl.Read, acl.Write, acl.Execute}},
{"rwx", []acl.Perm{acl.Read, acl.Write, acl.Write, acl.Execute}},
}
for _, tc := range testCases {
t.Run(tc.want, func(t *testing.T) {
a := &ACL{perms: tc.perms}
if got := a.String(); got != tc.want {
t.Errorf("String() = %v, want %v",
got, tc.want)
}
})
}
}
func permSubTestSuffix(perms []acl.Perm) (suffix string) {
for _, perm := range perms {
switch perm {
case acl.Read:
suffix += "_read"
case acl.Write:
suffix += "_write"
case acl.Execute:
suffix += "_execute"
default:
panic("unreachable")
}
}
return
}

View File

@ -1,73 +0,0 @@
package system
import (
"os"
"testing"
)
func TestEnsure(t *testing.T) {
testCases := []struct {
name string
perm os.FileMode
}{
{"/tmp/fortify.1971", 0701},
{"/tmp/fortify.1971/tmpdir", 0700},
{"/tmp/fortify.1971/tmpdir/150", 0700},
{"/run/user/1971/fortify", 0700},
}
for _, tc := range testCases {
t.Run(tc.name+"_"+tc.perm.String(), func(t *testing.T) {
sys := New(150)
sys.Ensure(tc.name, tc.perm)
(&tcOp{User, tc.name}).test(t, sys.ops, []Op{&Mkdir{User, tc.name, tc.perm, false}}, "Ensure")
})
}
}
func TestEphemeral(t *testing.T) {
testCases := []struct {
perm os.FileMode
tcOp
}{
{0700, tcOp{Process, "/run/user/1971/fortify/ec07546a772a07cde87389afc84ffd13"}},
{0701, tcOp{Process, "/tmp/fortify.1971/ec07546a772a07cde87389afc84ffd13"}},
}
for _, tc := range testCases {
t.Run(tc.path+"_"+tc.perm.String()+"_"+TypeString(tc.et), func(t *testing.T) {
sys := New(150)
sys.Ephemeral(tc.et, tc.path, tc.perm)
tc.test(t, sys.ops, []Op{&Mkdir{tc.et, tc.path, tc.perm, true}}, "Ephemeral")
})
}
}
func TestMkdir_String(t *testing.T) {
testCases := []struct {
want string
ephemeral bool
et Enablement
}{
{"Ensure", false, User},
{"Ensure", false, Process},
{"Ensure", false, EWayland},
{"Wayland", true, EWayland},
{"X11", true, EX11},
{"D-Bus", true, EDBus},
{"PulseAudio", true, EPulse},
}
for _, tc := range testCases {
t.Run(tc.want, func(t *testing.T) {
m := &Mkdir{
et: tc.et,
path: "/nonexistent",
perm: 0701,
ephemeral: tc.ephemeral,
}
want := "mode: " + os.FileMode(0701).String() + " type: " + tc.want + " path: \"/nonexistent\""
if got := m.String(); got != want {
t.Errorf("String() = %v, want %v", got, want)
}
})
}
}

View File

@ -1,79 +0,0 @@
package system
import "testing"
type tcOp struct {
et Enablement
path string
}
// test an instance of the Op interface
func (ptc tcOp) test(t *testing.T, gotOps []Op, wantOps []Op, fn string) {
if len(gotOps) != len(wantOps) {
t.Errorf("%s: inserted %v Ops, want %v", fn,
len(gotOps), len(wantOps))
return
}
t.Run("path", func(t *testing.T) {
if len(gotOps) > 0 {
if got := gotOps[0].Path(); got != ptc.path {
t.Errorf("Path() = %q, want %q",
got, ptc.path)
return
}
}
})
for i := range gotOps {
o := gotOps[i]
t.Run("is", func(t *testing.T) {
if !o.Is(o) {
t.Errorf("Is returned false on self")
return
}
if !o.Is(wantOps[i]) {
t.Errorf("%s: inserted %#v, want %#v",
fn,
o, wantOps[i])
return
}
})
t.Run("criteria", func(t *testing.T) {
testCases := []struct {
name string
ec *Criteria
want bool
}{
{"nil", newCriteria(), ptc.et != User},
{"self", newCriteria(ptc.et), true},
{"all", newCriteria(EWayland, EX11, EDBus, EPulse, User, Process), true},
{"enablements", newCriteria(EWayland, EX11, EDBus, EPulse), ptc.et != User && ptc.et != Process},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if got := tc.ec.hasType(o); got != tc.want {
t.Errorf("hasType: got %v, want %v",
got, tc.want)
}
})
}
})
}
}
func newCriteria(labels ...Enablement) *Criteria {
ec := new(Criteria)
if len(labels) == 0 {
return ec
}
ec.Enablements = new(Enablements)
for _, e := range labels {
ec.Set(e)
}
return ec
}

View File

@ -1,53 +0,0 @@
package system_test
import (
"strconv"
"testing"
"git.ophivana.moe/cat/fortify/internal/system"
)
func TestNew(t *testing.T) {
testCases := []struct {
uid int
}{
{150},
{149},
{148},
{147},
}
for _, tc := range testCases {
t.Run("sys initialised with uid "+strconv.Itoa(tc.uid), func(t *testing.T) {
if got := system.New(tc.uid); got.UID() != tc.uid {
t.Errorf("New(%d) uid = %d, want %d",
tc.uid,
got.UID(), tc.uid)
}
})
}
}
func TestTypeString(t *testing.T) {
testCases := []struct {
e system.Enablement
want string
}{
{system.EWayland, system.EWayland.String()},
{system.EX11, system.EX11.String()},
{system.EDBus, system.EDBus.String()},
{system.EPulse, system.EPulse.String()},
{system.User, "User"},
{system.Process, "Process"},
}
for _, tc := range testCases {
t.Run("label type string "+tc.want, func(t *testing.T) {
if got := system.TypeString(tc.e); got != tc.want {
t.Errorf("TypeString(%d) = %v, want %v",
tc.e,
got, tc.want)
}
})
}
}

View File

@ -1,167 +0,0 @@
package system
import (
"strconv"
"testing"
"git.ophivana.moe/cat/fortify/acl"
)
func TestCopyFile(t *testing.T) {
testCases := []struct {
dst, src string
}{
{"/tmp/fortify.1971/f587afe9fce3c8e1ad5b64deb6c41ad5/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie"},
{"/tmp/fortify.1971/62154f708b5184ab01f9dcc2bbe7a33b/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie"},
}
for _, tc := range testCases {
t.Run("copy file "+tc.dst+" from "+tc.src, func(t *testing.T) {
sys := New(150)
sys.CopyFile(tc.dst, tc.src)
(&tcOp{Process, tc.src}).test(t, sys.ops, []Op{
&Tmpfile{Process, tmpfileCopy, tc.dst, tc.src},
&ACL{Process, tc.dst, []acl.Perm{acl.Read}},
}, "CopyFile")
})
}
}
func TestCopyFileType(t *testing.T) {
testCases := []struct {
tcOp
dst string
}{
{tcOp{User, "/tmp/fortify.1971/f587afe9fce3c8e1ad5b64deb6c41ad5/pulse-cookie"}, "/home/ophestra/xdg/config/pulse/cookie"},
{tcOp{Process, "/tmp/fortify.1971/62154f708b5184ab01f9dcc2bbe7a33b/pulse-cookie"}, "/home/ophestra/xdg/config/pulse/cookie"},
}
for _, tc := range testCases {
t.Run("copy file "+tc.dst+" from "+tc.path+" with type "+TypeString(tc.et), func(t *testing.T) {
sys := New(150)
sys.CopyFileType(tc.et, tc.dst, tc.path)
tc.test(t, sys.ops, []Op{
&Tmpfile{tc.et, tmpfileCopy, tc.dst, tc.path},
&ACL{tc.et, tc.dst, []acl.Perm{acl.Read}},
}, "CopyFileType")
})
}
}
func TestLink(t *testing.T) {
testCases := []struct {
dst, src string
}{
{"/tmp/fortify.1971/f587afe9fce3c8e1ad5b64deb6c41ad5/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie"},
{"/tmp/fortify.1971/62154f708b5184ab01f9dcc2bbe7a33b/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie"},
}
for _, tc := range testCases {
t.Run("link file "+tc.dst+" from "+tc.src, func(t *testing.T) {
sys := New(150)
sys.Link(tc.src, tc.dst)
(&tcOp{Process, tc.src}).test(t, sys.ops, []Op{
&Tmpfile{Process, tmpfileLink, tc.dst, tc.src},
}, "Link")
})
}
}
func TestLinkFileType(t *testing.T) {
testCases := []struct {
tcOp
dst string
}{
{tcOp{User, "/tmp/fortify.1971/f587afe9fce3c8e1ad5b64deb6c41ad5/pulse-cookie"}, "/home/ophestra/xdg/config/pulse/cookie"},
{tcOp{Process, "/tmp/fortify.1971/62154f708b5184ab01f9dcc2bbe7a33b/pulse-cookie"}, "/home/ophestra/xdg/config/pulse/cookie"},
}
for _, tc := range testCases {
t.Run("link file "+tc.dst+" from "+tc.path+" with type "+TypeString(tc.et), func(t *testing.T) {
sys := New(150)
sys.LinkFileType(tc.et, tc.path, tc.dst)
tc.test(t, sys.ops, []Op{
&Tmpfile{tc.et, tmpfileLink, tc.dst, tc.path},
}, "LinkFileType")
})
}
}
func TestWrite(t *testing.T) {
testCases := []struct {
dst, src string
}{
{"/etc/passwd", "chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n"},
{"/etc/group", "fortify:x:65534:\n"},
}
for _, tc := range testCases {
t.Run("write "+strconv.Itoa(len(tc.src))+" bytes to "+tc.dst, func(t *testing.T) {
sys := New(150)
sys.Write(tc.dst, tc.src)
(&tcOp{Process, "(" + strconv.Itoa(len(tc.src)) + " bytes of data)"}).test(t, sys.ops, []Op{
&Tmpfile{Process, tmpfileWrite, tc.dst, tc.src},
&ACL{Process, tc.dst, []acl.Perm{acl.Read}},
}, "Write")
})
}
}
func TestWriteType(t *testing.T) {
testCases := []struct {
et Enablement
dst, src string
}{
{Process, "/etc/passwd", "chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n"},
{Process, "/etc/group", "fortify:x:65534:\n"},
{User, "/etc/passwd", "chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n"},
{User, "/etc/group", "fortify:x:65534:\n"},
}
for _, tc := range testCases {
t.Run("write "+strconv.Itoa(len(tc.src))+" bytes to "+tc.dst+" with type "+TypeString(tc.et), func(t *testing.T) {
sys := New(150)
sys.WriteType(tc.et, tc.dst, tc.src)
(&tcOp{tc.et, "(" + strconv.Itoa(len(tc.src)) + " bytes of data)"}).test(t, sys.ops, []Op{
&Tmpfile{tc.et, tmpfileWrite, tc.dst, tc.src},
&ACL{tc.et, tc.dst, []acl.Perm{acl.Read}},
}, "WriteType")
})
}
}
func TestTmpfile_String(t *testing.T) {
t.Run("invalid method panic", func(t *testing.T) {
defer func() {
wantPanic := "invalid tmpfile method 255"
if r := recover(); r != wantPanic {
t.Errorf("String() panic = %v, want %v",
r, wantPanic)
}
}()
_ = (&Tmpfile{method: 255}).String()
})
testCases := []struct {
method uint8
dst, src string
want string
}{
{tmpfileCopy, "/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie",
`"/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse-cookie" from "/home/ophestra/xdg/config/pulse/cookie"`},
{tmpfileLink, "/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/wayland", "/run/user/1971/wayland-0",
`"/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/wayland" from "/run/user/1971/wayland-0"`},
{tmpfileLink, "/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse", "/run/user/1971/pulse/native",
`"/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse" from "/run/user/1971/pulse/native"`},
{tmpfileWrite, "/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/passwd", "chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n",
`75 bytes of data to "/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/passwd"`},
{tmpfileWrite, "/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/group", "fortify:x:65534:\n",
`17 bytes of data to "/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/group"`},
}
for _, tc := range testCases {
t.Run(tc.want, func(t *testing.T) {
if got := (&Tmpfile{
method: tc.method,
dst: tc.dst,
src: tc.src,
}).String(); got != tc.want {
t.Errorf("String() = %v, want %v", got, tc.want)
}
})
}
}

View File

@ -1,34 +0,0 @@
package system
import (
"testing"
)
func TestChangeHosts(t *testing.T) {
testCases := []string{"chronos", "keyring", "cat", "kbd", "yonah"}
for _, tc := range testCases {
t.Run("append ChangeHosts operation for "+tc, func(t *testing.T) {
sys := New(150)
sys.ChangeHosts(tc)
(&tcOp{EX11, tc}).test(t, sys.ops, []Op{
XHost(tc),
}, "ChangeHosts")
})
}
}
func TestXHost_String(t *testing.T) {
testCases := []struct {
username string
want string
}{
{"chronos", "SI:localuser:chronos"},
}
for _, tc := range testCases {
t.Run(tc.want, func(t *testing.T) {
if got := XHost(tc.username).String(); got != tc.want {
t.Errorf("String() = %v, want %v", got, tc.want)
}
})
}
}

View File

@ -54,10 +54,8 @@ func main() {
// invoke app // invoke app
r := 1 r := 1
a, err := app.New() a := app.New()
if err != nil { if err := a.Seal(loadConfig()); err != nil {
fatalf("cannot create app: %s\n", err)
} else if err = a.Seal(loadConfig()); err != nil {
logBaseError(err, "fortify: cannot seal app:") logBaseError(err, "fortify: cannot seal app:")
} else if err = a.Start(); err != nil { } else if err = a.Start(); err != nil {
logBaseError(err, "fortify: cannot start app:") logBaseError(err, "fortify: cannot start app:")
@ -67,7 +65,7 @@ func main() {
} }
logWaitError(err) logWaitError(err)
} }
if err = a.WaitErr(); err != nil { if err := a.WaitErr(); err != nil {
fmt.Println("fortify: inner wait failed:", err) fmt.Println("fortify: inner wait failed:", err)
} }
os.Exit(r) os.Exit(r)

View File

@ -10,7 +10,7 @@
buildGoModule rec { buildGoModule rec {
pname = "fortify"; pname = "fortify";
version = "0.0.6"; version = "0.0.4";
src = ./.; src = ./.;
vendorHash = null; vendorHash = null;