diff --git a/internal/system/acl_test.go b/internal/system/acl_test.go new file mode 100644 index 0000000..37384e1 --- /dev/null +++ b/internal/system/acl_test.go @@ -0,0 +1,89 @@ +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 +} diff --git a/internal/system/mkdir_test.go b/internal/system/mkdir_test.go new file mode 100644 index 0000000..2082694 --- /dev/null +++ b/internal/system/mkdir_test.go @@ -0,0 +1,73 @@ +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) + } + }) + } +} diff --git a/internal/system/op_internal_test.go b/internal/system/op_internal_test.go new file mode 100644 index 0000000..7ec4f17 --- /dev/null +++ b/internal/system/op_internal_test.go @@ -0,0 +1,79 @@ +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 +} diff --git a/internal/system/op_test.go b/internal/system/op_test.go new file mode 100644 index 0000000..428c7ea --- /dev/null +++ b/internal/system/op_test.go @@ -0,0 +1,53 @@ +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) + } + }) + } +} diff --git a/internal/system/tmpfiles_test.go b/internal/system/tmpfiles_test.go new file mode 100644 index 0000000..afd92c7 --- /dev/null +++ b/internal/system/tmpfiles_test.go @@ -0,0 +1,167 @@ +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) + } + }) + } +} diff --git a/internal/system/xhost_test.go b/internal/system/xhost_test.go new file mode 100644 index 0000000..4b36443 --- /dev/null +++ b/internal/system/xhost_test.go @@ -0,0 +1,34 @@ +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) + } + }) + } +}