Compare commits
7 Commits
83e72c2b59
...
e85be67fd9
Author | SHA1 | Date | |
---|---|---|---|
e85be67fd9 | |||
7e69893264 | |||
38a3e6af03 | |||
90cb01b274 | |||
b1e1d5627e | |||
3ae2ab652e | |||
db71fbe22b |
69
acl/acl-update.c
Normal file
69
acl/acl-update.c
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#include "acl-update.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <sys/acl.h>
|
||||||
|
#include <acl/libacl.h>
|
||||||
|
|
||||||
|
int f_acl_update_file_by_uid(const char *path_p, uid_t uid, acl_perm_t *perms, size_t plen) {
|
||||||
|
int ret = -1;
|
||||||
|
bool v;
|
||||||
|
int i;
|
||||||
|
acl_t acl;
|
||||||
|
acl_entry_t entry;
|
||||||
|
acl_tag_t tag_type;
|
||||||
|
void *qualifier_p;
|
||||||
|
acl_permset_t permset;
|
||||||
|
|
||||||
|
acl = acl_get_file(path_p, ACL_TYPE_ACCESS);
|
||||||
|
if (acl == NULL)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
// prune entries by uid
|
||||||
|
for (i = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); i == 1; i = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry)) {
|
||||||
|
if (acl_get_tag_type(entry, &tag_type) != 0)
|
||||||
|
return -1;
|
||||||
|
if (tag_type != ACL_USER)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
qualifier_p = acl_get_qualifier(entry);
|
||||||
|
if (qualifier_p == NULL)
|
||||||
|
return -1;
|
||||||
|
v = *(uid_t *)qualifier_p == uid;
|
||||||
|
acl_free(qualifier_p);
|
||||||
|
|
||||||
|
if (!v)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
acl_delete_entry(acl, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plen == 0)
|
||||||
|
goto set;
|
||||||
|
|
||||||
|
if (acl_create_entry(&acl, &entry) != 0)
|
||||||
|
goto out;
|
||||||
|
if (acl_get_permset(entry, &permset) != 0)
|
||||||
|
goto out;
|
||||||
|
for (i = 0; i < plen; i++) {
|
||||||
|
if (acl_add_perm(permset, perms[i]) != 0)
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (acl_set_tag_type(entry, ACL_USER) != 0)
|
||||||
|
goto out;
|
||||||
|
if (acl_set_qualifier(entry, (void *)&uid) != 0)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
set:
|
||||||
|
if (acl_calc_mask(&acl) != 0)
|
||||||
|
goto out;
|
||||||
|
if (acl_valid(acl) != 0)
|
||||||
|
goto out;
|
||||||
|
if (acl_set_file(path_p, ACL_TYPE_ACCESS, acl) == 0)
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
out:
|
||||||
|
free((void *)path_p);
|
||||||
|
if (acl != NULL)
|
||||||
|
acl_free((void *)acl);
|
||||||
|
return ret;
|
||||||
|
}
|
3
acl/acl-update.h
Normal file
3
acl/acl-update.h
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#include <sys/acl.h>
|
||||||
|
|
||||||
|
int f_acl_update_file_by_uid(const char *path_p, uid_t uid, acl_perm_t *perms, size_t plen);
|
43
acl/acl.go
43
acl/acl.go
@ -1,19 +1,36 @@
|
|||||||
// Package acl implements simple ACL manipulation via libacl.
|
// Package acl implements simple ACL manipulation via libacl.
|
||||||
package acl
|
package acl
|
||||||
|
|
||||||
type Perms []Perm
|
/*
|
||||||
|
#cgo linux pkg-config: --static libacl
|
||||||
|
|
||||||
func (ps Perms) String() string {
|
#include "acl-update.h"
|
||||||
var s = []byte("---")
|
*/
|
||||||
for _, p := range ps {
|
import "C"
|
||||||
switch p {
|
|
||||||
case Read:
|
type Perm C.acl_perm_t
|
||||||
s[0] = 'r'
|
|
||||||
case Write:
|
const (
|
||||||
s[1] = 'w'
|
Read Perm = C.ACL_READ
|
||||||
case Execute:
|
Write Perm = C.ACL_WRITE
|
||||||
s[2] = 'x'
|
Execute Perm = C.ACL_EXECUTE
|
||||||
}
|
)
|
||||||
|
|
||||||
|
// Update replaces ACL_USER entry with qualifier uid.
|
||||||
|
func Update(name string, uid int, perms ...Perm) error {
|
||||||
|
var p *Perm
|
||||||
|
if len(perms) > 0 {
|
||||||
|
p = &perms[0]
|
||||||
}
|
}
|
||||||
return string(s)
|
|
||||||
|
r, err := C.f_acl_update_file_by_uid(
|
||||||
|
C.CString(name),
|
||||||
|
C.uid_t(uid),
|
||||||
|
(*C.acl_perm_t)(p),
|
||||||
|
C.size_t(len(perms)),
|
||||||
|
)
|
||||||
|
if r == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ func TestUpdatePerm(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("default clear mask", func(t *testing.T) {
|
t.Run("default clear mask", func(t *testing.T) {
|
||||||
if err := acl.UpdatePerm(testFilePath, uid); err != nil {
|
if err := acl.Update(testFilePath, uid); err != nil {
|
||||||
t.Fatalf("UpdatePerm: error = %v", err)
|
t.Fatalf("UpdatePerm: error = %v", err)
|
||||||
}
|
}
|
||||||
if cur = getfacl(t, testFilePath); len(cur) != 4 {
|
if cur = getfacl(t, testFilePath); len(cur) != 4 {
|
||||||
@ -56,7 +56,7 @@ func TestUpdatePerm(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("default clear consistency", func(t *testing.T) {
|
t.Run("default clear consistency", func(t *testing.T) {
|
||||||
if err := acl.UpdatePerm(testFilePath, uid); err != nil {
|
if err := acl.Update(testFilePath, uid); err != nil {
|
||||||
t.Fatalf("UpdatePerm: error = %v", err)
|
t.Fatalf("UpdatePerm: error = %v", err)
|
||||||
}
|
}
|
||||||
if val := getfacl(t, testFilePath); !reflect.DeepEqual(val, cur) {
|
if val := getfacl(t, testFilePath); !reflect.DeepEqual(val, cur) {
|
||||||
@ -76,7 +76,7 @@ func TestUpdatePerm(t *testing.T) {
|
|||||||
func testUpdate(t *testing.T, testFilePath, name string, cur []*getFAclResp, val fAclPerm, perms ...acl.Perm) {
|
func testUpdate(t *testing.T, testFilePath, name string, cur []*getFAclResp, val fAclPerm, perms ...acl.Perm) {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
if err := acl.UpdatePerm(testFilePath, uid); err != nil {
|
if err := acl.Update(testFilePath, uid); err != nil {
|
||||||
t.Fatalf("UpdatePerm: error = %v", err)
|
t.Fatalf("UpdatePerm: error = %v", err)
|
||||||
}
|
}
|
||||||
if v := getfacl(t, testFilePath); !reflect.DeepEqual(v, cur) {
|
if v := getfacl(t, testFilePath); !reflect.DeepEqual(v, cur) {
|
||||||
@ -84,7 +84,7 @@ func testUpdate(t *testing.T, testFilePath, name string, cur []*getFAclResp, val
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := acl.UpdatePerm(testFilePath, uid, perms...); err != nil {
|
if err := acl.Update(testFilePath, uid, perms...); err != nil {
|
||||||
t.Fatalf("UpdatePerm: error = %v", err)
|
t.Fatalf("UpdatePerm: error = %v", err)
|
||||||
}
|
}
|
||||||
r := respByCred(getfacl(t, testFilePath), fAclTypeUser, cred)
|
r := respByCred(getfacl(t, testFilePath), fAclTypeUser, cred)
|
||||||
|
196
acl/c.go
196
acl/c.go
@ -1,196 +0,0 @@
|
|||||||
package acl
|
|
||||||
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"runtime"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo linux pkg-config: --static libacl
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <sys/acl.h>
|
|
||||||
#include <acl/libacl.h>
|
|
||||||
|
|
||||||
static acl_t _go_acl_get_file(const char *path_p, acl_type_t type) {
|
|
||||||
acl_t acl = acl_get_file(path_p, type);
|
|
||||||
free((void *)path_p);
|
|
||||||
return acl;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _go_acl_set_file(const char *path_p, acl_type_t type, acl_t acl) {
|
|
||||||
if (acl_valid(acl) != 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ret = acl_set_file(path_p, type, acl);
|
|
||||||
free((void *)path_p);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
func getFile(name string, t C.acl_type_t) (*ACL, error) {
|
|
||||||
a, err := C._go_acl_get_file(C.CString(name), t)
|
|
||||||
if errors.Is(err, syscall.ENODATA) {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return newACL(a), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (acl *ACL) setFile(name string, t C.acl_type_t) error {
|
|
||||||
_, err := C._go_acl_set_file(C.CString(name), t, acl.acl)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func newACL(a C.acl_t) *ACL {
|
|
||||||
acl := &ACL{a}
|
|
||||||
runtime.SetFinalizer(acl, (*ACL).free)
|
|
||||||
return acl
|
|
||||||
}
|
|
||||||
|
|
||||||
type ACL struct {
|
|
||||||
acl C.acl_t
|
|
||||||
}
|
|
||||||
|
|
||||||
func (acl *ACL) free() {
|
|
||||||
C.acl_free(unsafe.Pointer(acl.acl))
|
|
||||||
|
|
||||||
// no need for a finalizer anymore
|
|
||||||
runtime.SetFinalizer(acl, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
Read = C.ACL_READ
|
|
||||||
Write = C.ACL_WRITE
|
|
||||||
Execute = C.ACL_EXECUTE
|
|
||||||
|
|
||||||
TypeDefault = C.ACL_TYPE_DEFAULT
|
|
||||||
TypeAccess = C.ACL_TYPE_ACCESS
|
|
||||||
|
|
||||||
UndefinedTag = C.ACL_UNDEFINED_TAG
|
|
||||||
UserObj = C.ACL_USER_OBJ
|
|
||||||
User = C.ACL_USER
|
|
||||||
GroupObj = C.ACL_GROUP_OBJ
|
|
||||||
Group = C.ACL_GROUP
|
|
||||||
Mask = C.ACL_MASK
|
|
||||||
Other = C.ACL_OTHER
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
Perm C.acl_perm_t
|
|
||||||
)
|
|
||||||
|
|
||||||
func (acl *ACL) removeEntry(tt C.acl_tag_t, tq int) error {
|
|
||||||
var e C.acl_entry_t
|
|
||||||
|
|
||||||
// get first entry
|
|
||||||
if r, err := C.acl_get_entry(acl.acl, C.ACL_FIRST_ENTRY, &e); err != nil {
|
|
||||||
return err
|
|
||||||
} else if r == 0 {
|
|
||||||
// return on acl with no entries
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
if r, err := C.acl_get_entry(acl.acl, C.ACL_NEXT_ENTRY, &e); err != nil {
|
|
||||||
return err
|
|
||||||
} else if r == 0 {
|
|
||||||
// return on drained acl
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
q int
|
|
||||||
t C.acl_tag_t
|
|
||||||
)
|
|
||||||
|
|
||||||
// get current entry tag type
|
|
||||||
if _, err := C.acl_get_tag_type(e, &t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// get current entry qualifier
|
|
||||||
if rq, err := C.acl_get_qualifier(e); err != nil {
|
|
||||||
// neither ACL_USER nor ACL_GROUP
|
|
||||||
if errors.Is(err, syscall.EINVAL) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
q = *(*int)(rq)
|
|
||||||
C.acl_free(rq)
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete on match
|
|
||||||
if t == tt && q == tq {
|
|
||||||
_, err := C.acl_delete_entry(acl.acl, e)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdatePerm(name string, uid int, perms ...Perm) error {
|
|
||||||
// read acl from file
|
|
||||||
a, err := getFile(name, TypeAccess)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// free acl on return if get is successful
|
|
||||||
defer a.free()
|
|
||||||
|
|
||||||
// remove existing entry
|
|
||||||
if err = a.removeEntry(User, uid); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// create new entry if perms are passed
|
|
||||||
if len(perms) > 0 {
|
|
||||||
// create new acl entry
|
|
||||||
var e C.acl_entry_t
|
|
||||||
if _, err = C.acl_create_entry(&a.acl, &e); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// get perm set of new entry
|
|
||||||
var p C.acl_permset_t
|
|
||||||
if _, err = C.acl_get_permset(e, &p); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// add target perms
|
|
||||||
for _, perm := range perms {
|
|
||||||
if _, err = C.acl_add_perm(p, C.acl_perm_t(perm)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set perm set to new entry
|
|
||||||
if _, err = C.acl_set_permset(e, p); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// set user tag to new entry
|
|
||||||
if _, err = C.acl_set_tag_type(e, User); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// set qualifier (uid) to new entry
|
|
||||||
if _, err = C.acl_set_qualifier(e, unsafe.Pointer(&uid)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate mask after update
|
|
||||||
if _, err = C.acl_calc_mask(&a.acl); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// write acl to file
|
|
||||||
return a.setFile(name, TypeAccess)
|
|
||||||
}
|
|
18
acl/perms.go
Normal file
18
acl/perms.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package acl
|
||||||
|
|
||||||
|
type Perms []Perm
|
||||||
|
|
||||||
|
func (ps Perms) String() string {
|
||||||
|
var s = []byte("---")
|
||||||
|
for _, p := range ps {
|
||||||
|
switch p {
|
||||||
|
case Read:
|
||||||
|
s[0] = 'r'
|
||||||
|
case Write:
|
||||||
|
s[1] = 'w'
|
||||||
|
case Execute:
|
||||||
|
s[2] = 'x'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(s)
|
||||||
|
}
|
@ -6,7 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/dbus"
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
"git.gensokyo.uk/security/fortify/internal/system"
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
type bundleInfo struct {
|
type bundleInfo struct {
|
||||||
|
@ -3,7 +3,7 @@ package fst
|
|||||||
import (
|
import (
|
||||||
"git.gensokyo.uk/security/fortify/dbus"
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||||
"git.gensokyo.uk/security/fortify/internal/system"
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Tmp = "/.fortify"
|
const Tmp = "/.fortify"
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/dbus"
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||||
"git.gensokyo.uk/security/fortify/internal/system"
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testCasesNixos = []sealTestCase{
|
var testCasesNixos = []sealTestCase{
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package app_test
|
package app_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/acl"
|
"git.gensokyo.uk/security/fortify/acl"
|
||||||
"git.gensokyo.uk/security/fortify/dbus"
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||||
"git.gensokyo.uk/security/fortify/internal/system"
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testCasesPd = []sealTestCase{
|
var testCasesPd = []sealTestCase{
|
||||||
@ -217,7 +219,7 @@ var testCasesPd = []sealTestCase{
|
|||||||
Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
|
Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
|
||||||
Ephemeral(system.Process, "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c", acl.Execute).
|
Ephemeral(system.Process, "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c", acl.Execute).
|
||||||
Ensure("/tmp/fortify.1971/wayland", 0711).
|
Ensure("/tmp/fortify.1971/wayland", 0711).
|
||||||
Wayland("/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/1971/wayland-0", "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c").
|
Wayland(new(*os.File), "/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/1971/wayland-0", "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c").
|
||||||
Link("/run/user/1971/pulse/native", "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse").
|
Link("/run/user/1971/pulse/native", "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse").
|
||||||
CopyFile(new([]byte), "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
|
CopyFile(new([]byte), "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
|
||||||
MustProxyDBus("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", &dbus.Config{
|
MustProxyDBus("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", &dbus.Config{
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||||
"git.gensokyo.uk/security/fortify/internal/app"
|
"git.gensokyo.uk/security/fortify/internal/app"
|
||||||
"git.gensokyo.uk/security/fortify/internal/linux"
|
"git.gensokyo.uk/security/fortify/internal/linux"
|
||||||
"git.gensokyo.uk/security/fortify/internal/system"
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
type sealTestCase struct {
|
type sealTestCase struct {
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||||
"git.gensokyo.uk/security/fortify/internal/linux"
|
"git.gensokyo.uk/security/fortify/internal/linux"
|
||||||
"git.gensokyo.uk/security/fortify/internal/system"
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewWithID(id fst.ID, os linux.System) App {
|
func NewWithID(id fst.ID, os linux.System) App {
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
"git.gensokyo.uk/security/fortify/internal/linux"
|
"git.gensokyo.uk/security/fortify/internal/linux"
|
||||||
"git.gensokyo.uk/security/fortify/internal/state"
|
"git.gensokyo.uk/security/fortify/internal/state"
|
||||||
"git.gensokyo.uk/security/fortify/internal/system"
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -252,8 +252,12 @@ func (a *app) Seal(config *fst.Config) error {
|
|||||||
// store activity begins after Start is called and must end before Wait
|
// store activity begins after Start is called and must end before Wait
|
||||||
seal.store = state.NewMulti(seal.RunDirPath)
|
seal.store = state.NewMulti(seal.RunDirPath)
|
||||||
|
|
||||||
// initialise system interface with full uid
|
// initialise system interface with os uid
|
||||||
seal.sys.I = system.New(seal.sys.user.uid)
|
seal.sys.I = system.New(seal.sys.user.uid)
|
||||||
|
seal.sys.I.IsVerbose = fmsg.Load
|
||||||
|
seal.sys.I.Verbose = fmsg.Verbose
|
||||||
|
seal.sys.I.Verbosef = fmsg.Verbosef
|
||||||
|
seal.sys.I.WrapErr = fmsg.WrapError
|
||||||
|
|
||||||
// pass through enablements
|
// pass through enablements
|
||||||
seal.et = config.Confinement.Enablements
|
seal.et = config.Confinement.Enablements
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
"git.gensokyo.uk/security/fortify/internal/linux"
|
"git.gensokyo.uk/security/fortify/internal/linux"
|
||||||
"git.gensokyo.uk/security/fortify/internal/system"
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
"git.gensokyo.uk/security/fortify/wl"
|
"git.gensokyo.uk/security/fortify/wl"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -163,7 +163,7 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error {
|
|||||||
// use instance ID in case app id is not set
|
// use instance ID in case app id is not set
|
||||||
appID = "uk.gensokyo.fortify." + seal.id
|
appID = "uk.gensokyo.fortify." + seal.id
|
||||||
}
|
}
|
||||||
seal.sys.Wayland(outerPath, socketPath, appID, seal.id)
|
seal.sys.Wayland(&seal.sys.sp, outerPath, socketPath, appID, seal.id)
|
||||||
seal.sys.bwrap.Bind(outerPath, innerPath)
|
seal.sys.bwrap.Bind(outerPath, innerPath)
|
||||||
} else { // bind mount wayland socket (insecure)
|
} else { // bind mount wayland socket (insecure)
|
||||||
fmsg.Verbose("direct wayland access, PROCEED WITH CAUTION")
|
fmsg.Verbose("direct wayland access, PROCEED WITH CAUTION")
|
||||||
|
@ -14,7 +14,7 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/internal/app/shim"
|
"git.gensokyo.uk/security/fortify/internal/app/shim"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
"git.gensokyo.uk/security/fortify/internal/state"
|
"git.gensokyo.uk/security/fortify/internal/state"
|
||||||
"git.gensokyo.uk/security/fortify/internal/system"
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
const shimSetupTimeout = 5 * time.Second
|
const shimSetupTimeout = 5 * time.Second
|
||||||
@ -58,7 +58,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
|
|||||||
if startTime, err := a.shim.Start(
|
if startTime, err := a.shim.Start(
|
||||||
a.seal.sys.user.as,
|
a.seal.sys.user.as,
|
||||||
a.seal.sys.user.supp,
|
a.seal.sys.user.supp,
|
||||||
a.seal.sys.Sync(),
|
a.seal.sys.sp,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||||
"git.gensokyo.uk/security/fortify/internal/system"
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
// appSealSys encapsulates app seal behaviour with OS interactions
|
// appSealSys encapsulates app seal behaviour with OS interactions
|
||||||
type appSealSys struct {
|
type appSealSys struct {
|
||||||
bwrap *bwrap.Config
|
bwrap *bwrap.Config
|
||||||
|
// bwrap sync fd
|
||||||
|
sp *os.File
|
||||||
// paths to override by mounting tmpfs over them
|
// paths to override by mounting tmpfs over them
|
||||||
override []string
|
override []string
|
||||||
|
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/acl"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
"git.gensokyo.uk/security/fortify/wl"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Wayland sets up a wayland socket with a security context attached.
|
|
||||||
func (sys *I) Wayland(dst, src, appID, instanceID string) *I {
|
|
||||||
sys.lock.Lock()
|
|
||||||
defer sys.lock.Unlock()
|
|
||||||
|
|
||||||
sys.ops = append(sys.ops, Wayland{[2]string{dst, src}, new(wl.Conn), appID, instanceID})
|
|
||||||
|
|
||||||
return sys
|
|
||||||
}
|
|
||||||
|
|
||||||
type Wayland struct {
|
|
||||||
pair [2]string
|
|
||||||
conn *wl.Conn
|
|
||||||
|
|
||||||
appID, instanceID string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w Wayland) Type() Enablement {
|
|
||||||
return Process
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w Wayland) apply(sys *I) error {
|
|
||||||
// the Wayland op is not repeatable
|
|
||||||
if sys.sp != nil {
|
|
||||||
return errors.New("attempted to attach multiple wayland sockets")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := w.conn.Attach(w.pair[1]); err != nil {
|
|
||||||
// make console output less nasty
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
err = os.ErrNotExist
|
|
||||||
}
|
|
||||||
return fmsg.WrapErrorSuffix(err,
|
|
||||||
fmt.Sprintf("cannot attach to wayland on %q:", w.pair[1]))
|
|
||||||
} else {
|
|
||||||
fmsg.Verbosef("wayland attached on %q", w.pair[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
if sp, err := w.conn.Bind(w.pair[0], w.appID, w.instanceID); err != nil {
|
|
||||||
return fmsg.WrapErrorSuffix(err,
|
|
||||||
fmt.Sprintf("cannot bind to socket on %q:", w.pair[0]))
|
|
||||||
} else {
|
|
||||||
sys.sp = sp
|
|
||||||
fmsg.Verbosef("wayland listening on %q", w.pair[0])
|
|
||||||
return fmsg.WrapErrorSuffix(errors.Join(os.Chmod(w.pair[0], 0), acl.UpdatePerm(w.pair[0], sys.uid, acl.Read, acl.Write, acl.Execute)),
|
|
||||||
fmt.Sprintf("cannot chmod socket on %q:", w.pair[0]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w Wayland) revert(_ *I, ec *Criteria) error {
|
|
||||||
if ec.hasType(w) {
|
|
||||||
fmsg.Verbosef("removing wayland socket on %q", w.pair[0])
|
|
||||||
if err := os.Remove(w.pair[0]); err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmsg.Verbosef("detaching from wayland on %q", w.pair[1])
|
|
||||||
return fmsg.WrapErrorSuffix(w.conn.Close(),
|
|
||||||
fmt.Sprintf("cannot detach from wayland on %q:", w.pair[1]))
|
|
||||||
} else {
|
|
||||||
fmsg.Verbosef("skipping wayland cleanup on %q", w.pair[0])
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w Wayland) Is(o Op) bool {
|
|
||||||
w0, ok := o.(Wayland)
|
|
||||||
return ok && w.pair == w0.pair
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w Wayland) Path() string {
|
|
||||||
return w.pair[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w Wayland) String() string {
|
|
||||||
return fmt.Sprintf("wayland socket at %q", w.pair[0])
|
|
||||||
}
|
|
2
main.go
2
main.go
@ -26,7 +26,7 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
"git.gensokyo.uk/security/fortify/internal/linux"
|
"git.gensokyo.uk/security/fortify/internal/linux"
|
||||||
"git.gensokyo.uk/security/fortify/internal/state"
|
"git.gensokyo.uk/security/fortify/internal/state"
|
||||||
"git.gensokyo.uk/security/fortify/internal/system"
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/acl"
|
"git.gensokyo.uk/security/fortify/acl"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// UpdatePerm appends an ephemeral acl update Op.
|
// UpdatePerm appends an ephemeral acl update Op.
|
||||||
@ -31,23 +30,21 @@ type ACL struct {
|
|||||||
perms acl.Perms
|
perms acl.Perms
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACL) Type() Enablement {
|
func (a *ACL) Type() Enablement { return a.et }
|
||||||
return a.et
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *ACL) apply(sys *I) error {
|
func (a *ACL) apply(sys *I) error {
|
||||||
fmsg.Verbose("applying ACL", a)
|
sys.println("applying ACL", a)
|
||||||
return fmsg.WrapErrorSuffix(acl.UpdatePerm(a.path, sys.uid, a.perms...),
|
return sys.wrapErrSuffix(acl.Update(a.path, sys.uid, a.perms...),
|
||||||
fmt.Sprintf("cannot apply ACL entry to %q:", a.path))
|
fmt.Sprintf("cannot apply ACL entry to %q:", a.path))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACL) revert(sys *I, ec *Criteria) error {
|
func (a *ACL) revert(sys *I, ec *Criteria) error {
|
||||||
if ec.hasType(a) {
|
if ec.hasType(a) {
|
||||||
fmsg.Verbose("stripping ACL", a)
|
sys.println("stripping ACL", a)
|
||||||
return fmsg.WrapErrorSuffix(acl.UpdatePerm(a.path, sys.uid),
|
return sys.wrapErrSuffix(acl.Update(a.path, sys.uid),
|
||||||
fmt.Sprintf("cannot strip ACL entry from %q:", a.path))
|
fmt.Sprintf("cannot strip ACL entry from %q:", a.path))
|
||||||
} else {
|
} else {
|
||||||
fmsg.Verbose("skipping ACL", a)
|
sys.println("skipping ACL", a)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,9 +57,7 @@ func (a *ACL) Is(o Op) bool {
|
|||||||
slices.Equal(a.perms, a0.perms)
|
slices.Equal(a.perms, a0.perms)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACL) Path() string {
|
func (a *ACL) Path() string { return a.path }
|
||||||
return a.path
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *ACL) String() string {
|
func (a *ACL) String() string {
|
||||||
return fmt.Sprintf("%s type: %s path: %q",
|
return fmt.Sprintf("%s type: %s path: %q",
|
@ -8,7 +8,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/dbus"
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -28,7 +27,7 @@ func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath st
|
|||||||
|
|
||||||
// session bus is mandatory
|
// session bus is mandatory
|
||||||
if session == nil {
|
if session == nil {
|
||||||
return nil, fmsg.WrapError(ErrDBusConfig,
|
return nil, sys.wrapErr(ErrDBusConfig,
|
||||||
"attempted to seal message bus proxy without session bus config")
|
"attempted to seal message bus proxy without session bus config")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,12 +47,12 @@ func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath st
|
|||||||
d.proxy = dbus.New(sessionBus, systemBus)
|
d.proxy = dbus.New(sessionBus, systemBus)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if fmsg.Load() && d.proxy.Sealed() {
|
if sys.IsVerbose() && d.proxy.Sealed() {
|
||||||
fmsg.Verbose("sealed session proxy", session.Args(sessionBus))
|
sys.println("sealed session proxy", session.Args(sessionBus))
|
||||||
if system != nil {
|
if system != nil {
|
||||||
fmsg.Verbose("sealed system proxy", system.Args(systemBus))
|
sys.println("sealed system proxy", system.Args(systemBus))
|
||||||
}
|
}
|
||||||
fmsg.Verbose("message bus proxy final args:", d.proxy)
|
sys.println("message bus proxy final args:", d.proxy)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -62,7 +61,7 @@ func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath st
|
|||||||
|
|
||||||
// seal dbus proxy
|
// seal dbus proxy
|
||||||
d.out = &scanToFmsg{msg: new(strings.Builder)}
|
d.out = &scanToFmsg{msg: new(strings.Builder)}
|
||||||
return d.out.Dump, fmsg.WrapErrorSuffix(d.proxy.Seal(session, system),
|
return d.out.Dump, sys.wrapErrSuffix(d.proxy.Seal(session, system),
|
||||||
"cannot seal message bus proxy:")
|
"cannot seal message bus proxy:")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,32 +73,30 @@ type DBus struct {
|
|||||||
system bool
|
system bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DBus) Type() Enablement {
|
func (d *DBus) Type() Enablement { return Process }
|
||||||
return Process
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DBus) apply(sys *I) error {
|
func (d *DBus) apply(sys *I) error {
|
||||||
fmsg.Verbosef("session bus proxy on %q for upstream %q", d.proxy.Session()[1], d.proxy.Session()[0])
|
sys.printf("session bus proxy on %q for upstream %q", d.proxy.Session()[1], d.proxy.Session()[0])
|
||||||
if d.system {
|
if d.system {
|
||||||
fmsg.Verbosef("system bus proxy on %q for upstream %q", d.proxy.System()[1], d.proxy.System()[0])
|
sys.printf("system bus proxy on %q for upstream %q", d.proxy.System()[1], d.proxy.System()[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// this starts the process and blocks until ready
|
// this starts the process and blocks until ready
|
||||||
if err := d.proxy.Start(sys.ctx, d.out, true); err != nil {
|
if err := d.proxy.Start(sys.ctx, d.out, true); err != nil {
|
||||||
d.out.Dump()
|
d.out.Dump()
|
||||||
return fmsg.WrapErrorSuffix(err,
|
return sys.wrapErrSuffix(err,
|
||||||
"cannot start message bus proxy:")
|
"cannot start message bus proxy:")
|
||||||
}
|
}
|
||||||
fmsg.Verbose("starting message bus proxy:", d.proxy)
|
sys.println("starting message bus proxy:", d.proxy)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DBus) revert(_ *I, _ *Criteria) error {
|
func (d *DBus) revert(sys *I, _ *Criteria) error {
|
||||||
// criteria ignored here since dbus is always process-scoped
|
// criteria ignored here since dbus is always process-scoped
|
||||||
fmsg.Verbose("terminating message bus proxy")
|
sys.println("terminating message bus proxy")
|
||||||
d.proxy.Close()
|
d.proxy.Close()
|
||||||
defer fmsg.Verbose("message bus proxy exit")
|
defer sys.println("message bus proxy exit")
|
||||||
return fmsg.WrapErrorSuffix(d.proxy.Wait(), "message bus proxy error:")
|
return sys.wrapErrSuffix(d.proxy.Wait(), "message bus proxy error:")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DBus) Is(o Op) bool {
|
func (d *DBus) Is(o Op) bool {
|
@ -3,8 +3,6 @@ package system
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Link registers an Op that links dst to src.
|
// Link registers an Op that links dst to src.
|
||||||
@ -27,27 +25,23 @@ type Hardlink struct {
|
|||||||
|
|
||||||
func (l *Hardlink) Type() Enablement { return l.et }
|
func (l *Hardlink) Type() Enablement { return l.et }
|
||||||
|
|
||||||
func (l *Hardlink) apply(_ *I) error {
|
func (l *Hardlink) apply(sys *I) error {
|
||||||
fmsg.Verbose("linking", l)
|
sys.println("linking", l)
|
||||||
return fmsg.WrapErrorSuffix(os.Link(l.src, l.dst),
|
return sys.wrapErrSuffix(os.Link(l.src, l.dst),
|
||||||
fmt.Sprintf("cannot link %q:", l.dst))
|
fmt.Sprintf("cannot link %q:", l.dst))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Hardlink) revert(_ *I, ec *Criteria) error {
|
func (l *Hardlink) revert(sys *I, ec *Criteria) error {
|
||||||
if ec.hasType(l) {
|
if ec.hasType(l) {
|
||||||
fmsg.Verbosef("removing hard link %q", l.dst)
|
sys.printf("removing hard link %q", l.dst)
|
||||||
return fmsg.WrapErrorSuffix(os.Remove(l.dst),
|
return sys.wrapErrSuffix(os.Remove(l.dst),
|
||||||
fmt.Sprintf("cannot remove hard link %q:", l.dst))
|
fmt.Sprintf("cannot remove hard link %q:", l.dst))
|
||||||
} else {
|
} else {
|
||||||
fmsg.Verbosef("skipping hard link %q", l.dst)
|
sys.printf("skipping hard link %q", l.dst)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Hardlink) Is(o Op) bool {
|
func (l *Hardlink) Is(o Op) bool { l0, ok := o.(*Hardlink); return ok && l0 != nil && *l == *l0 }
|
||||||
l0, ok := o.(*Hardlink)
|
|
||||||
return ok && l0 != nil && *l == *l0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Hardlink) Path() string { return l.src }
|
func (l *Hardlink) Path() string { return l.src }
|
||||||
func (l *Hardlink) String() string { return fmt.Sprintf("%q from %q", l.dst, l.src) }
|
func (l *Hardlink) String() string { return fmt.Sprintf("%q from %q", l.dst, l.src) }
|
@ -4,8 +4,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure the existence and mode of a directory.
|
// Ensure the existence and mode of a directory.
|
||||||
@ -39,33 +37,33 @@ func (m *Mkdir) Type() Enablement {
|
|||||||
return m.et
|
return m.et
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mkdir) apply(_ *I) error {
|
func (m *Mkdir) apply(sys *I) error {
|
||||||
fmsg.Verbose("ensuring directory", m)
|
sys.println("ensuring directory", m)
|
||||||
|
|
||||||
// create directory
|
// create directory
|
||||||
err := os.Mkdir(m.path, m.perm)
|
err := os.Mkdir(m.path, m.perm)
|
||||||
if !errors.Is(err, os.ErrExist) {
|
if !errors.Is(err, os.ErrExist) {
|
||||||
return fmsg.WrapErrorSuffix(err,
|
return sys.wrapErrSuffix(err,
|
||||||
fmt.Sprintf("cannot create directory %q:", m.path))
|
fmt.Sprintf("cannot create directory %q:", m.path))
|
||||||
}
|
}
|
||||||
|
|
||||||
// directory exists, ensure mode
|
// directory exists, ensure mode
|
||||||
return fmsg.WrapErrorSuffix(os.Chmod(m.path, m.perm),
|
return sys.wrapErrSuffix(os.Chmod(m.path, m.perm),
|
||||||
fmt.Sprintf("cannot change mode of %q to %s:", m.path, m.perm))
|
fmt.Sprintf("cannot change mode of %q to %s:", m.path, m.perm))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mkdir) revert(_ *I, ec *Criteria) error {
|
func (m *Mkdir) revert(sys *I, ec *Criteria) error {
|
||||||
if !m.ephemeral {
|
if !m.ephemeral {
|
||||||
// skip non-ephemeral dir and do not log anything
|
// skip non-ephemeral dir and do not log anything
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if ec.hasType(m) {
|
if ec.hasType(m) {
|
||||||
fmsg.Verbose("destroying ephemeral directory", m)
|
sys.println("destroying ephemeral directory", m)
|
||||||
return fmsg.WrapErrorSuffix(os.Remove(m.path),
|
return sys.wrapErrSuffix(os.Remove(m.path),
|
||||||
fmt.Sprintf("cannot remove ephemeral directory %q:", m.path))
|
fmt.Sprintf("cannot remove ephemeral directory %q:", m.path))
|
||||||
} else {
|
} else {
|
||||||
fmsg.Verbose("skipping ephemeral directory", m)
|
sys.println("skipping ephemeral directory", m)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,10 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -56,12 +53,26 @@ func TypeString(e Enablement) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New initialises sys with no-op verbose functions.
|
||||||
|
func New(uid int) (sys *I) {
|
||||||
|
sys = new(I)
|
||||||
|
sys.uid = uid
|
||||||
|
sys.IsVerbose = func() bool { return false }
|
||||||
|
sys.Verbose = func(...any) {}
|
||||||
|
sys.Verbosef = func(string, ...any) {}
|
||||||
|
sys.WrapErr = func(err error, _ ...any) error { return err }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
type I struct {
|
type I struct {
|
||||||
uid int
|
uid int
|
||||||
ops []Op
|
ops []Op
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
// sync fd passed to bwrap
|
|
||||||
sp *os.File
|
IsVerbose func() bool
|
||||||
|
Verbose func(v ...any)
|
||||||
|
Verbosef func(format string, v ...any)
|
||||||
|
WrapErr func(err error, a ...any) error
|
||||||
|
|
||||||
// whether sys has been reverted
|
// whether sys has been reverted
|
||||||
state bool
|
state bool
|
||||||
@ -69,12 +80,15 @@ type I struct {
|
|||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sys *I) UID() int {
|
func (sys *I) UID() int { return sys.uid }
|
||||||
return sys.uid
|
func (sys *I) println(v ...any) { sys.Verbose(v...) }
|
||||||
}
|
func (sys *I) printf(format string, v ...any) { sys.Verbosef(format, v...) }
|
||||||
|
func (sys *I) wrapErr(err error, a ...any) error { return sys.WrapErr(err, a...) }
|
||||||
func (sys *I) Sync() *os.File {
|
func (sys *I) wrapErrSuffix(err error, a ...any) error {
|
||||||
return sys.sp
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return sys.wrapErr(err, append(a, err)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sys *I) Equal(v *I) bool {
|
func (sys *I) Equal(v *I) bool {
|
||||||
@ -106,7 +120,7 @@ func (sys *I) Commit(ctx context.Context) error {
|
|||||||
// sp is set to nil when all ops are applied
|
// sp is set to nil when all ops are applied
|
||||||
if sp != nil {
|
if sp != nil {
|
||||||
// rollback partial commit
|
// rollback partial commit
|
||||||
fmsg.Verbosef("commit faulted after %d ops, rolling back partial commit", len(sp.ops))
|
sys.printf("commit faulted after %d ops, rolling back partial commit", len(sp.ops))
|
||||||
if err := sp.Revert(&Criteria{nil}); err != nil {
|
if err := sp.Revert(&Criteria{nil}); err != nil {
|
||||||
log.Println("errors returned reverting partial commit:", err)
|
log.Println("errors returned reverting partial commit:", err)
|
||||||
}
|
}
|
||||||
@ -146,7 +160,3 @@ func (sys *I) Revert(ec *Criteria) error {
|
|||||||
// errors.Join filters nils
|
// errors.Join filters nils
|
||||||
return errors.Join(errs...)
|
return errors.Join(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(uid int) *I {
|
|
||||||
return &I{uid: uid}
|
|
||||||
}
|
|
@ -4,7 +4,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/system"
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNew(t *testing.T) {
|
func TestNew(t *testing.T) {
|
@ -2,12 +2,11 @@ package system
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CopyFile registers an Op that copies from src.
|
// CopyFile registers an Op that copies from src.
|
||||||
@ -32,29 +31,34 @@ type Tmpfile struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tmpfile) Type() Enablement { return Process }
|
func (t *Tmpfile) Type() Enablement { return Process }
|
||||||
func (t *Tmpfile) apply(_ *I) error {
|
func (t *Tmpfile) apply(sys *I) error {
|
||||||
fmsg.Verbose("copying", t)
|
sys.println("copying", t)
|
||||||
|
|
||||||
|
if t.payload == nil {
|
||||||
|
// this is a misuse of the API; do not return an error message
|
||||||
|
return errors.New("invalid payload")
|
||||||
|
}
|
||||||
|
|
||||||
if b, err := os.Stat(t.src); err != nil {
|
if b, err := os.Stat(t.src); err != nil {
|
||||||
return fmsg.WrapErrorSuffix(err,
|
return sys.wrapErrSuffix(err,
|
||||||
fmt.Sprintf("cannot stat %q:", t.src))
|
fmt.Sprintf("cannot stat %q:", t.src))
|
||||||
} else {
|
} else {
|
||||||
if b.IsDir() {
|
if b.IsDir() {
|
||||||
return fmsg.WrapErrorSuffix(syscall.EISDIR,
|
return sys.wrapErrSuffix(syscall.EISDIR,
|
||||||
fmt.Sprintf("%q is a directory", t.src))
|
fmt.Sprintf("%q is a directory", t.src))
|
||||||
}
|
}
|
||||||
if s := b.Size(); s > t.n {
|
if s := b.Size(); s > t.n {
|
||||||
return fmsg.WrapErrorSuffix(syscall.ENOMEM,
|
return sys.wrapErrSuffix(syscall.ENOMEM,
|
||||||
fmt.Sprintf("file %q is too long: %d > %d",
|
fmt.Sprintf("file %q is too long: %d > %d",
|
||||||
t.src, s, t.n))
|
t.src, s, t.n))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if f, err := os.Open(t.src); err != nil {
|
if f, err := os.Open(t.src); err != nil {
|
||||||
return fmsg.WrapErrorSuffix(err,
|
return sys.wrapErrSuffix(err,
|
||||||
fmt.Sprintf("cannot open %q:", t.src))
|
fmt.Sprintf("cannot open %q:", t.src))
|
||||||
} else if _, err = io.CopyN(t.buf, f, t.n); err != nil {
|
} else if _, err = io.CopyN(t.buf, f, t.n); err != nil {
|
||||||
return fmsg.WrapErrorSuffix(err,
|
return sys.wrapErrSuffix(err,
|
||||||
fmt.Sprintf("cannot read from %q:", t.src))
|
fmt.Sprintf("cannot read from %q:", t.src))
|
||||||
}
|
}
|
||||||
|
|
89
system/wayland.go
Normal file
89
system/wayland.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/acl"
|
||||||
|
"git.gensokyo.uk/security/fortify/wl"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wayland sets up a wayland socket with a security context attached.
|
||||||
|
func (sys *I) Wayland(syncFd **os.File, dst, src, appID, instanceID string) *I {
|
||||||
|
sys.lock.Lock()
|
||||||
|
defer sys.lock.Unlock()
|
||||||
|
|
||||||
|
sys.ops = append(sys.ops, &Wayland{syncFd, dst, src, appID, instanceID, wl.Conn{}})
|
||||||
|
|
||||||
|
return sys
|
||||||
|
}
|
||||||
|
|
||||||
|
type Wayland struct {
|
||||||
|
sync **os.File
|
||||||
|
dst, src string
|
||||||
|
appID, instanceID string
|
||||||
|
|
||||||
|
conn wl.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Wayland) Type() Enablement { return Process }
|
||||||
|
|
||||||
|
func (w *Wayland) apply(sys *I) error {
|
||||||
|
if w.sync == nil {
|
||||||
|
// this is a misuse of the API; do not return an error message
|
||||||
|
return errors.New("invalid sync")
|
||||||
|
}
|
||||||
|
|
||||||
|
// the Wayland op is not repeatable
|
||||||
|
if *w.sync != nil {
|
||||||
|
// this is a misuse of the API; do not return an error message
|
||||||
|
return errors.New("attempted to attach multiple wayland sockets")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.conn.Attach(w.src); err != nil {
|
||||||
|
// make console output less nasty
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
err = os.ErrNotExist
|
||||||
|
}
|
||||||
|
return sys.wrapErrSuffix(err,
|
||||||
|
fmt.Sprintf("cannot attach to wayland on %q:", w.src))
|
||||||
|
} else {
|
||||||
|
sys.printf("wayland attached on %q", w.src)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sp, err := w.conn.Bind(w.dst, w.appID, w.instanceID); err != nil {
|
||||||
|
return sys.wrapErrSuffix(err,
|
||||||
|
fmt.Sprintf("cannot bind to socket on %q:", w.dst))
|
||||||
|
} else {
|
||||||
|
*w.sync = sp
|
||||||
|
sys.printf("wayland listening on %q", w.dst)
|
||||||
|
return sys.wrapErrSuffix(errors.Join(os.Chmod(w.dst, 0), acl.Update(w.dst, sys.uid, acl.Read, acl.Write, acl.Execute)),
|
||||||
|
fmt.Sprintf("cannot chmod socket on %q:", w.dst))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Wayland) revert(sys *I, ec *Criteria) error {
|
||||||
|
if ec.hasType(w) {
|
||||||
|
sys.printf("removing wayland socket on %q", w.dst)
|
||||||
|
if err := os.Remove(w.dst); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.printf("detaching from wayland on %q", w.src)
|
||||||
|
return sys.wrapErrSuffix(w.conn.Close(),
|
||||||
|
fmt.Sprintf("cannot detach from wayland on %q:", w.src))
|
||||||
|
} else {
|
||||||
|
sys.printf("skipping wayland cleanup on %q", w.dst)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Wayland) Is(o Op) bool {
|
||||||
|
w0, ok := o.(*Wayland)
|
||||||
|
return ok && w.dst == w0.dst && w.src == w0.src &&
|
||||||
|
w.appID == w0.appID && w.instanceID == w0.instanceID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Wayland) Path() string { return w.dst }
|
||||||
|
func (w *Wayland) String() string { return fmt.Sprintf("wayland socket at %q", w.dst) }
|
@ -3,8 +3,7 @@ package system
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/system/internal/xcb"
|
||||||
"git.gensokyo.uk/security/fortify/xcb"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ChangeHosts appends an X11 ChangeHosts command Op.
|
// ChangeHosts appends an X11 ChangeHosts command Op.
|
||||||
@ -23,19 +22,19 @@ func (x XHost) Type() Enablement {
|
|||||||
return EX11
|
return EX11
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x XHost) apply(_ *I) error {
|
func (x XHost) apply(sys *I) error {
|
||||||
fmsg.Verbosef("inserting entry %s to X11", x)
|
sys.printf("inserting entry %s to X11", x)
|
||||||
return fmsg.WrapErrorSuffix(xcb.ChangeHosts(xcb.HostModeInsert, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)),
|
return sys.wrapErrSuffix(xcb.ChangeHosts(xcb.HostModeInsert, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)),
|
||||||
fmt.Sprintf("cannot insert entry %s to X11:", x))
|
fmt.Sprintf("cannot insert entry %s to X11:", x))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x XHost) revert(_ *I, ec *Criteria) error {
|
func (x XHost) revert(sys *I, ec *Criteria) error {
|
||||||
if ec.hasType(x) {
|
if ec.hasType(x) {
|
||||||
fmsg.Verbosef("deleting entry %s from X11", x)
|
sys.printf("deleting entry %s from X11", x)
|
||||||
return fmsg.WrapErrorSuffix(xcb.ChangeHosts(xcb.HostModeDelete, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)),
|
return sys.wrapErrSuffix(xcb.ChangeHosts(xcb.HostModeDelete, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)),
|
||||||
fmt.Sprintf("cannot delete entry %s from X11:", x))
|
fmt.Sprintf("cannot delete entry %s from X11:", x))
|
||||||
} else {
|
} else {
|
||||||
fmsg.Verbosef("skipping entry %s in X11", x)
|
sys.printf("skipping entry %s in X11", x)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user