Compare commits
9 Commits
7638a44fa6
...
52e3324ef4
| Author | SHA1 | Date | |
|---|---|---|---|
|
52e3324ef4
|
|||
|
f95e0a7568
|
|||
|
4c647add0d
|
|||
|
a341466942
|
|||
|
e4ee8df83c
|
|||
|
048c1957f1
|
|||
|
790d77075e
|
|||
|
e5ff40e7d3
|
|||
|
123d7fbfd5
|
@@ -147,11 +147,6 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
||||
Enablements: hst.NewEnablements(et),
|
||||
|
||||
Container: &hst.ContainerConfig{
|
||||
Userns: true,
|
||||
HostNet: true,
|
||||
Tty: true,
|
||||
HostAbstract: true,
|
||||
|
||||
Filesystem: []hst.FilesystemConfigJSON{
|
||||
// autoroot, includes the home directory
|
||||
{FilesystemConfig: &hst.FSBind{
|
||||
@@ -167,6 +162,8 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
||||
|
||||
Path: progPath,
|
||||
Args: args,
|
||||
|
||||
Flags: hst.FUserns | hst.FHostNet | hst.FHostAbstract | hst.FTty,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -87,19 +87,19 @@ func printShowInstance(
|
||||
t.Printf(" Hostname:\t%s\n", params.Hostname)
|
||||
}
|
||||
flags := make([]string, 0, 7)
|
||||
writeFlag := func(name string, value bool) {
|
||||
if value {
|
||||
writeFlag := func(name string, flag uintptr, force bool) {
|
||||
if params.Flags&flag != 0 || force {
|
||||
flags = append(flags, name)
|
||||
}
|
||||
}
|
||||
writeFlag("userns", params.Userns)
|
||||
writeFlag("devel", params.Devel)
|
||||
writeFlag("net", params.HostNet)
|
||||
writeFlag("abstract", params.HostAbstract)
|
||||
writeFlag("device", params.Device)
|
||||
writeFlag("tty", params.Tty)
|
||||
writeFlag("mapuid", params.MapRealUID)
|
||||
writeFlag("directwl", config.DirectWayland)
|
||||
writeFlag("userns", hst.FUserns, false)
|
||||
writeFlag("devel", hst.FDevel, false)
|
||||
writeFlag("net", hst.FHostNet, false)
|
||||
writeFlag("abstract", hst.FHostAbstract, false)
|
||||
writeFlag("device", hst.FDevice, false)
|
||||
writeFlag("tty", hst.FTty, false)
|
||||
writeFlag("mapuid", hst.FMapRealUID, false)
|
||||
writeFlag("directwl", 0, config.DirectWayland)
|
||||
if len(flags) == 0 {
|
||||
flags = append(flags, "none")
|
||||
}
|
||||
@@ -129,11 +129,8 @@ func printShowInstance(
|
||||
}
|
||||
if len(config.ExtraPerms) > 0 {
|
||||
t.Printf("Extra ACL\n")
|
||||
for _, p := range config.ExtraPerms {
|
||||
if p == nil {
|
||||
continue
|
||||
}
|
||||
t.Printf(" %s\n", p.String())
|
||||
for i := range config.ExtraPerms {
|
||||
t.Printf(" %s\n", config.ExtraPerms[i].String())
|
||||
}
|
||||
t.Printf("\n")
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ App
|
||||
Flags: none
|
||||
|
||||
`, false},
|
||||
{"config nil entries", nil, &hst.Config{Container: &hst.ContainerConfig{Filesystem: make([]hst.FilesystemConfigJSON, 1)}, ExtraPerms: make([]*hst.ExtraPermConfig, 1)}, false, false, `Error: container configuration missing path to home directory!
|
||||
{"config nil entries", nil, &hst.Config{Container: &hst.ContainerConfig{Filesystem: make([]hst.FilesystemConfigJSON, 1)}, ExtraPerms: make([]hst.ExtraPermConfig, 1)}, false, false, `Error: container configuration missing path to home directory!
|
||||
|
||||
App
|
||||
Identity: 0
|
||||
@@ -99,6 +99,7 @@ Filesystem
|
||||
<invalid>
|
||||
|
||||
Extra ACL
|
||||
<invalid>
|
||||
|
||||
`, false},
|
||||
{"config pd dbus see", nil, &hst.Config{SessionBus: &hst.BusConfig{See: []string{"org.example.test"}}}, false, false, `Error: configuration missing container state!
|
||||
@@ -252,20 +253,11 @@ App
|
||||
"container": {
|
||||
"hostname": "localhost",
|
||||
"wait_delay": -1,
|
||||
"seccomp_compat": true,
|
||||
"devel": true,
|
||||
"userns": true,
|
||||
"host_net": true,
|
||||
"host_abstract": true,
|
||||
"tty": true,
|
||||
"multiarch": true,
|
||||
"env": {
|
||||
"GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
|
||||
"GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
|
||||
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
|
||||
},
|
||||
"map_real_uid": true,
|
||||
"device": true,
|
||||
"filesystem": [
|
||||
{
|
||||
"type": "bind",
|
||||
@@ -331,7 +323,16 @@ App
|
||||
"--disable-smooth-scrolling",
|
||||
"--enable-features=UseOzonePlatform",
|
||||
"--ozone-platform=wayland"
|
||||
]
|
||||
],
|
||||
"seccomp_compat": true,
|
||||
"devel": true,
|
||||
"userns": true,
|
||||
"host_net": true,
|
||||
"host_abstract": true,
|
||||
"tty": true,
|
||||
"multiarch": true,
|
||||
"map_real_uid": true,
|
||||
"device": true
|
||||
}
|
||||
},
|
||||
"time": "1970-01-01T00:00:00.000000009Z"
|
||||
@@ -402,20 +403,11 @@ App
|
||||
"container": {
|
||||
"hostname": "localhost",
|
||||
"wait_delay": -1,
|
||||
"seccomp_compat": true,
|
||||
"devel": true,
|
||||
"userns": true,
|
||||
"host_net": true,
|
||||
"host_abstract": true,
|
||||
"tty": true,
|
||||
"multiarch": true,
|
||||
"env": {
|
||||
"GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
|
||||
"GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
|
||||
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
|
||||
},
|
||||
"map_real_uid": true,
|
||||
"device": true,
|
||||
"filesystem": [
|
||||
{
|
||||
"type": "bind",
|
||||
@@ -481,7 +473,16 @@ App
|
||||
"--disable-smooth-scrolling",
|
||||
"--enable-features=UseOzonePlatform",
|
||||
"--ozone-platform=wayland"
|
||||
]
|
||||
],
|
||||
"seccomp_compat": true,
|
||||
"devel": true,
|
||||
"userns": true,
|
||||
"host_net": true,
|
||||
"host_abstract": true,
|
||||
"tty": true,
|
||||
"multiarch": true,
|
||||
"map_real_uid": true,
|
||||
"device": true
|
||||
}
|
||||
}
|
||||
`, true},
|
||||
@@ -612,20 +613,11 @@ func TestPrintPs(t *testing.T) {
|
||||
"container": {
|
||||
"hostname": "localhost",
|
||||
"wait_delay": -1,
|
||||
"seccomp_compat": true,
|
||||
"devel": true,
|
||||
"userns": true,
|
||||
"host_net": true,
|
||||
"host_abstract": true,
|
||||
"tty": true,
|
||||
"multiarch": true,
|
||||
"env": {
|
||||
"GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
|
||||
"GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
|
||||
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
|
||||
},
|
||||
"map_real_uid": true,
|
||||
"device": true,
|
||||
"filesystem": [
|
||||
{
|
||||
"type": "bind",
|
||||
@@ -691,7 +683,16 @@ func TestPrintPs(t *testing.T) {
|
||||
"--disable-smooth-scrolling",
|
||||
"--enable-features=UseOzonePlatform",
|
||||
"--ozone-platform=wayland"
|
||||
]
|
||||
],
|
||||
"seccomp_compat": true,
|
||||
"devel": true,
|
||||
"userns": true,
|
||||
"host_net": true,
|
||||
"host_abstract": true,
|
||||
"tty": true,
|
||||
"multiarch": true,
|
||||
"map_real_uid": true,
|
||||
"device": true
|
||||
}
|
||||
},
|
||||
"time": "1970-01-01T00:00:00.000000009Z"
|
||||
|
||||
@@ -77,14 +77,6 @@ func (app *appInfo) toHst(pathSet *appPathSet, pathname *check.Absolute, argv []
|
||||
|
||||
Container: &hst.ContainerConfig{
|
||||
Hostname: formatHostname(app.Name),
|
||||
Devel: app.Devel,
|
||||
Userns: app.Userns,
|
||||
HostNet: app.HostNet,
|
||||
HostAbstract: app.HostAbstract,
|
||||
Device: app.Device,
|
||||
Tty: app.Tty || flagDropShell,
|
||||
MapRealUID: app.MapRealUID,
|
||||
Multiarch: app.Multiarch,
|
||||
Filesystem: []hst.FilesystemConfigJSON{
|
||||
{FilesystemConfig: &hst.FSBind{Target: fhs.AbsEtc, Source: pathSet.cacheDir.Append("etc"), Special: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath.Append("store"), Target: pathNixStore}},
|
||||
@@ -108,11 +100,36 @@ func (app *appInfo) toHst(pathSet *appPathSet, pathname *check.Absolute, argv []
|
||||
Path: pathname,
|
||||
Args: argv,
|
||||
},
|
||||
ExtraPerms: []*hst.ExtraPermConfig{
|
||||
ExtraPerms: []hst.ExtraPermConfig{
|
||||
{Path: dataHome, Execute: true},
|
||||
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
||||
},
|
||||
}
|
||||
|
||||
if app.Devel {
|
||||
config.Container.Flags |= hst.FDevel
|
||||
}
|
||||
if app.Userns {
|
||||
config.Container.Flags |= hst.FUserns
|
||||
}
|
||||
if app.HostNet {
|
||||
config.Container.Flags |= hst.FHostNet
|
||||
}
|
||||
if app.HostAbstract {
|
||||
config.Container.Flags |= hst.FHostAbstract
|
||||
}
|
||||
if app.Device {
|
||||
config.Container.Flags |= hst.FDevice
|
||||
}
|
||||
if app.Tty || flagDropShell {
|
||||
config.Container.Flags |= hst.FTty
|
||||
}
|
||||
if app.MapRealUID {
|
||||
config.Container.Flags |= hst.FMapRealUID
|
||||
}
|
||||
if app.Multiarch {
|
||||
config.Container.Flags |= hst.FMultiarch
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,18 @@ func withNixDaemon(
|
||||
action string, command []string, net bool, updateConfig func(config *hst.Config) *hst.Config,
|
||||
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func(),
|
||||
) {
|
||||
flags := hst.FMultiarch | hst.FUserns // nix sandbox requires userns
|
||||
if net {
|
||||
flags |= hst.FHostNet
|
||||
}
|
||||
if dropShell {
|
||||
flags |= hst.FTty
|
||||
}
|
||||
|
||||
mustRunAppDropShell(ctx, msg, updateConfig(&hst.Config{
|
||||
ID: app.ID,
|
||||
|
||||
ExtraPerms: []*hst.ExtraPermConfig{
|
||||
ExtraPerms: []hst.ExtraPermConfig{
|
||||
{Path: dataHome, Execute: true},
|
||||
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
||||
},
|
||||
@@ -29,10 +37,7 @@ func withNixDaemon(
|
||||
|
||||
Container: &hst.ContainerConfig{
|
||||
Hostname: formatHostname(app.Name) + "-" + action,
|
||||
Userns: true, // nix sandbox requires userns
|
||||
HostNet: net,
|
||||
Multiarch: true,
|
||||
Tty: dropShell,
|
||||
|
||||
Filesystem: []hst.FilesystemConfigJSON{
|
||||
{FilesystemConfig: &hst.FSBind{Target: fhs.AbsEtc, Source: pathSet.cacheDir.Append("etc"), Special: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath, Target: pathNix, Write: true}},
|
||||
@@ -58,6 +63,8 @@ func withNixDaemon(
|
||||
// terminate nix-daemon
|
||||
" && pkill nix-daemon",
|
||||
},
|
||||
|
||||
Flags: flags,
|
||||
},
|
||||
}), dropShell, beforeFail)
|
||||
}
|
||||
@@ -66,11 +73,17 @@ func withCacheDir(
|
||||
ctx context.Context,
|
||||
msg message.Msg,
|
||||
action string, command []string, workDir *check.Absolute,
|
||||
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func()) {
|
||||
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func(),
|
||||
) {
|
||||
flags := hst.FMultiarch
|
||||
if dropShell {
|
||||
flags |= hst.FTty
|
||||
}
|
||||
|
||||
mustRunAppDropShell(ctx, msg, &hst.Config{
|
||||
ID: app.ID,
|
||||
|
||||
ExtraPerms: []*hst.ExtraPermConfig{
|
||||
ExtraPerms: []hst.ExtraPermConfig{
|
||||
{Path: dataHome, Execute: true},
|
||||
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
||||
{Path: workDir, Execute: true},
|
||||
@@ -80,8 +93,7 @@ func withCacheDir(
|
||||
|
||||
Container: &hst.ContainerConfig{
|
||||
Hostname: formatHostname(app.Name) + "-" + action,
|
||||
Multiarch: true,
|
||||
Tty: dropShell,
|
||||
|
||||
Filesystem: []hst.FilesystemConfigJSON{
|
||||
{FilesystemConfig: &hst.FSBind{Target: fhs.AbsEtc, Source: workDir.Append(fhs.Etc), Special: true}},
|
||||
{FilesystemConfig: &hst.FSBind{Source: workDir.Append("nix"), Target: pathNix}},
|
||||
@@ -98,6 +110,8 @@ func withCacheDir(
|
||||
|
||||
Path: pathShell,
|
||||
Args: []string{bash, "-lc", strings.Join(command, " && ")},
|
||||
|
||||
Flags: flags,
|
||||
},
|
||||
}, dropShell, beforeFail)
|
||||
}
|
||||
|
||||
@@ -576,13 +576,12 @@ const (
|
||||
func init() {
|
||||
helperCommands = append(helperCommands, func(c command.Command) {
|
||||
c.Command("block", command.UsageInternal, func(args []string) error {
|
||||
if _, err := os.NewFile(3, "sync").Write([]byte{0}); err != nil {
|
||||
return fmt.Errorf("write to sync pipe: %v", err)
|
||||
}
|
||||
{
|
||||
sig := make(chan os.Signal, 1)
|
||||
signal.Notify(sig, os.Interrupt)
|
||||
go func() { <-sig; os.Exit(blockExitCodeInterrupt) }()
|
||||
|
||||
if _, err := os.NewFile(3, "sync").Write([]byte{0}); err != nil {
|
||||
return fmt.Errorf("write to sync pipe: %v", err)
|
||||
}
|
||||
select {}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package seccomp_test
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
|
||||
"hakurei.app/container/bits"
|
||||
@@ -12,18 +13,18 @@ type (
|
||||
seccomp.ExportFlag
|
||||
bits.FilterPreset
|
||||
}
|
||||
bpfLookup map[bpfPreset][]byte
|
||||
bpfLookup map[bpfPreset][sha512.Size]byte
|
||||
)
|
||||
|
||||
func toHash(s string) []byte {
|
||||
if len(s) != 128 {
|
||||
func toHash(s string) [sha512.Size]byte {
|
||||
if len(s) != sha512.Size*2 {
|
||||
panic("bad sha512 string length")
|
||||
}
|
||||
if v, err := hex.DecodeString(s); err != nil {
|
||||
panic(err.Error())
|
||||
} else if len(v) != 64 {
|
||||
} else if len(v) != sha512.Size {
|
||||
panic("unreachable")
|
||||
} else {
|
||||
return v
|
||||
return ([sha512.Size]byte)(v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,14 +9,16 @@
|
||||
|
||||
#define LEN(arr) (sizeof(arr) / sizeof((arr)[0]))
|
||||
|
||||
int32_t hakurei_export_filter(int *ret_p, int fd, uint32_t arch,
|
||||
uint32_t multiarch,
|
||||
int32_t hakurei_scmp_make_filter(int *ret_p, uintptr_t allocate_p,
|
||||
uint32_t arch, uint32_t multiarch,
|
||||
struct hakurei_syscall_rule *rules,
|
||||
size_t rules_sz, hakurei_export_flag flags) {
|
||||
int i;
|
||||
int last_allowed_family;
|
||||
int disallowed;
|
||||
struct hakurei_syscall_rule *rule;
|
||||
void *buf;
|
||||
size_t len = 0;
|
||||
|
||||
int32_t res = 0; /* refer to resPrefix for message */
|
||||
|
||||
@@ -108,14 +110,26 @@ int32_t hakurei_export_filter(int *ret_p, int fd, uint32_t arch,
|
||||
seccomp_rule_add_exact(ctx, SCMP_ACT_ERRNO(EAFNOSUPPORT), SCMP_SYS(socket), 1,
|
||||
SCMP_A0(SCMP_CMP_GE, last_allowed_family + 1));
|
||||
|
||||
if (fd < 0) {
|
||||
if (allocate_p == 0) {
|
||||
*ret_p = seccomp_load(ctx);
|
||||
if (*ret_p != 0) {
|
||||
res = 7;
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
*ret_p = seccomp_export_bpf(ctx, fd);
|
||||
*ret_p = seccomp_export_bpf_mem(ctx, NULL, &len);
|
||||
if (*ret_p != 0) {
|
||||
res = 6;
|
||||
goto out;
|
||||
}
|
||||
|
||||
buf = hakurei_scmp_allocate(allocate_p, len);
|
||||
if (buf == NULL) {
|
||||
res = 4;
|
||||
goto out;
|
||||
}
|
||||
|
||||
*ret_p = seccomp_export_bpf_mem(ctx, buf, &len);
|
||||
if (*ret_p != 0) {
|
||||
res = 6;
|
||||
goto out;
|
||||
|
||||
@@ -18,7 +18,8 @@ struct hakurei_syscall_rule {
|
||||
struct scmp_arg_cmp *arg;
|
||||
};
|
||||
|
||||
int32_t hakurei_export_filter(int *ret_p, int fd, uint32_t arch,
|
||||
uint32_t multiarch,
|
||||
extern void *hakurei_scmp_allocate(uintptr_t f, size_t len);
|
||||
int32_t hakurei_scmp_make_filter(int *ret_p, uintptr_t allocate_p,
|
||||
uint32_t arch, uint32_t multiarch,
|
||||
struct hakurei_syscall_rule *rules,
|
||||
size_t rules_sz, hakurei_export_flag flags);
|
||||
@@ -3,7 +3,7 @@ package seccomp
|
||||
/*
|
||||
#cgo linux pkg-config: --static libseccomp
|
||||
|
||||
#include <libseccomp-helper.h>
|
||||
#include "libseccomp-helper.h"
|
||||
#include <sys/personality.h>
|
||||
*/
|
||||
import "C"
|
||||
@@ -11,23 +11,21 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"runtime/cgo"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
PER_LINUX = C.PER_LINUX
|
||||
PER_LINUX32 = C.PER_LINUX32
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidRules = errors.New("invalid native rules slice")
|
||||
)
|
||||
// ErrInvalidRules is returned for a zero-length rules slice.
|
||||
var ErrInvalidRules = errors.New("invalid native rules slice")
|
||||
|
||||
// LibraryError represents a libseccomp error.
|
||||
type LibraryError struct {
|
||||
// User facing description of the libseccomp function returning the error.
|
||||
Prefix string
|
||||
// Negated errno value returned by libseccomp.
|
||||
Seccomp syscall.Errno
|
||||
// Global errno value on return.
|
||||
Errno error
|
||||
}
|
||||
|
||||
@@ -56,7 +54,9 @@ func (e *LibraryError) Is(err error) bool {
|
||||
}
|
||||
|
||||
type (
|
||||
// ScmpSyscall represents a syscall number passed to libseccomp via [NativeRule.Syscall].
|
||||
ScmpSyscall = C.int
|
||||
// ScmpErrno represents an errno value passed to libseccomp via [NativeRule.Errno].
|
||||
ScmpErrno = C.int
|
||||
)
|
||||
|
||||
@@ -88,12 +88,23 @@ var resPrefix = [...]string{
|
||||
3: "seccomp_arch_add failed (multiarch)",
|
||||
4: "internal libseccomp failure",
|
||||
5: "seccomp_rule_add failed",
|
||||
6: "seccomp_export_bpf failed",
|
||||
6: "seccomp_export_bpf_mem failed",
|
||||
7: "seccomp_load failed",
|
||||
}
|
||||
|
||||
// Export streams filter contents to fd, or installs it to the current process if fd < 0.
|
||||
func Export(fd int, rules []NativeRule, flags ExportFlag) error {
|
||||
// cbAllocateBuffer is the function signature for the function handle passed to hakurei_export_filter
|
||||
// which allocates the buffer that the resulting bpf program is copied into, and writes its slice header
|
||||
// to a value held by the caller.
|
||||
type cbAllocateBuffer = func(len C.size_t) (buf unsafe.Pointer)
|
||||
|
||||
//export hakurei_scmp_allocate
|
||||
func hakurei_scmp_allocate(f C.uintptr_t, len C.size_t) (buf unsafe.Pointer) {
|
||||
return cgo.Handle(f).Value().(cbAllocateBuffer)(len)
|
||||
}
|
||||
|
||||
// makeFilter generates a bpf program from a slice of [NativeRule] and writes the resulting byte slice to p.
|
||||
// The filter is installed to the current process if p is nil.
|
||||
func makeFilter(rules []NativeRule, flags ExportFlag, p *[]byte) error {
|
||||
if len(rules) == 0 {
|
||||
return ErrInvalidRules
|
||||
}
|
||||
@@ -117,33 +128,56 @@ func Export(fd int, rules []NativeRule, flags ExportFlag) error {
|
||||
|
||||
var ret C.int
|
||||
|
||||
var rulesPinner runtime.Pinner
|
||||
var scmpPinner runtime.Pinner
|
||||
for i := range rules {
|
||||
rule := &rules[i]
|
||||
rulesPinner.Pin(rule)
|
||||
scmpPinner.Pin(rule)
|
||||
if rule.Arg != nil {
|
||||
rulesPinner.Pin(rule.Arg)
|
||||
scmpPinner.Pin(rule.Arg)
|
||||
}
|
||||
}
|
||||
res, err := C.hakurei_export_filter(
|
||||
&ret, C.int(fd),
|
||||
|
||||
var allocateP cgo.Handle
|
||||
if p != nil {
|
||||
allocateP = cgo.NewHandle(func(len C.size_t) (buf unsafe.Pointer) {
|
||||
// this is so the slice header gets a Go pointer
|
||||
*p = make([]byte, len)
|
||||
|
||||
buf = unsafe.Pointer(unsafe.SliceData(*p))
|
||||
scmpPinner.Pin(buf)
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
res, err := C.hakurei_scmp_make_filter(
|
||||
&ret, C.uintptr_t(allocateP),
|
||||
arch, multiarch,
|
||||
(*C.struct_hakurei_syscall_rule)(unsafe.Pointer(&rules[0])),
|
||||
C.size_t(len(rules)),
|
||||
flags,
|
||||
)
|
||||
rulesPinner.Unpin()
|
||||
scmpPinner.Unpin()
|
||||
if p != nil {
|
||||
allocateP.Delete()
|
||||
}
|
||||
|
||||
if prefix := resPrefix[res]; prefix != "" {
|
||||
return &LibraryError{
|
||||
prefix,
|
||||
-syscall.Errno(ret),
|
||||
err,
|
||||
}
|
||||
return &LibraryError{prefix, syscall.Errno(-ret), err}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Export generates a bpf program from a slice of [NativeRule].
|
||||
// Errors returned by libseccomp is wrapped in [LibraryError].
|
||||
func Export(rules []NativeRule, flags ExportFlag) (data []byte, err error) {
|
||||
err = makeFilter(rules, flags, &data)
|
||||
return
|
||||
}
|
||||
|
||||
// Load generates a bpf program from a slice of [NativeRule] and enforces it on the current process.
|
||||
// Errors returned by libseccomp is wrapped in [LibraryError].
|
||||
func Load(rules []NativeRule, flags ExportFlag) error { return makeFilter(rules, flags, nil) }
|
||||
|
||||
// ScmpCompare is the equivalent of scmp_compare;
|
||||
// Comparison operators
|
||||
type ScmpCompare = C.enum_scmp_compare
|
||||
@@ -184,7 +218,15 @@ type ScmpArgCmp struct {
|
||||
DatumA, DatumB ScmpDatum
|
||||
}
|
||||
|
||||
// only used for testing
|
||||
const (
|
||||
// PersonaLinux is passed in a [ScmpDatum] for filtering calls to syscall.SYS_PERSONALITY.
|
||||
PersonaLinux = C.PER_LINUX
|
||||
// PersonaLinux32 is passed in a [ScmpDatum] for filtering calls to syscall.SYS_PERSONALITY.
|
||||
PersonaLinux32 = C.PER_LINUX32
|
||||
)
|
||||
|
||||
// syscallResolveName resolves a syscall number by name via seccomp_syscall_resolve_name.
|
||||
// This function is only for testing the lookup tables and included here for convenience.
|
||||
func syscallResolveName(s string) (trap int) {
|
||||
v := C.CString(s)
|
||||
trap = int(C.seccomp_syscall_resolve_name(v))
|
||||
|
||||
@@ -3,8 +3,6 @@ package seccomp_test
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"errors"
|
||||
"io"
|
||||
"slices"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
@@ -12,6 +10,67 @@ import (
|
||||
. "hakurei.app/container/seccomp"
|
||||
)
|
||||
|
||||
func TestLibraryError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
sample *LibraryError
|
||||
want string
|
||||
wantIs bool
|
||||
compare error
|
||||
}{
|
||||
{
|
||||
"full",
|
||||
&LibraryError{Prefix: "seccomp_export_bpf failed", Seccomp: syscall.ECANCELED, Errno: syscall.EBADF},
|
||||
"seccomp_export_bpf failed: operation canceled (bad file descriptor)",
|
||||
true,
|
||||
&LibraryError{Prefix: "seccomp_export_bpf failed", Seccomp: syscall.ECANCELED, Errno: syscall.EBADF},
|
||||
},
|
||||
{
|
||||
"errno only",
|
||||
&LibraryError{Prefix: "seccomp_init failed", Errno: syscall.ENOMEM},
|
||||
"seccomp_init failed: cannot allocate memory",
|
||||
false,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"seccomp only",
|
||||
&LibraryError{Prefix: "internal libseccomp failure", Seccomp: syscall.EFAULT},
|
||||
"internal libseccomp failure: bad address",
|
||||
true,
|
||||
syscall.EFAULT,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if errors.Is(tc.sample, tc.compare) != tc.wantIs {
|
||||
t.Errorf("errors.Is(%#v, %#v) did not return %v",
|
||||
tc.sample, tc.compare, tc.wantIs)
|
||||
}
|
||||
|
||||
if got := tc.sample.Error(); got != tc.want {
|
||||
t.Errorf("Error: %q, want %q",
|
||||
got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
wantPanic := "invalid libseccomp error"
|
||||
defer func() {
|
||||
if r := recover(); r != wantPanic {
|
||||
t.Errorf("panic: %q, want %q", r, wantPanic)
|
||||
}
|
||||
}()
|
||||
_ = new(LibraryError).Error()
|
||||
})
|
||||
}
|
||||
|
||||
func TestExport(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -38,61 +97,34 @@ func TestExport(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := New(Preset(tc.presets, tc.flags), tc.flags)
|
||||
want := bpfExpected[bpfPreset{tc.flags, tc.presets}]
|
||||
digest := sha512.New()
|
||||
|
||||
if _, err := io.Copy(digest, e); (err != nil) != tc.wantErr {
|
||||
t.Errorf("Exporter: error = %v, wantErr %v", err, tc.wantErr)
|
||||
if data, err := Export(Preset(tc.presets, tc.flags), tc.flags); (err != nil) != tc.wantErr {
|
||||
t.Errorf("Export: error = %v, wantErr %v", err, tc.wantErr)
|
||||
return
|
||||
}
|
||||
if err := e.Close(); err != nil {
|
||||
t.Errorf("Close: error = %v", err)
|
||||
}
|
||||
if got := digest.Sum(nil); !slices.Equal(got, want) {
|
||||
t.Fatalf("Export: hash = %x, want %x",
|
||||
got, want)
|
||||
} else if got := sha512.Sum512(data); got != want {
|
||||
t.Fatalf("Export: hash = %x, want %x", got, want)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("close without use", func(t *testing.T) {
|
||||
e := New(Preset(0, 0), 0)
|
||||
if err := e.Close(); !errors.Is(err, syscall.EINVAL) {
|
||||
t.Errorf("Close: error = %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("close partial read", func(t *testing.T) {
|
||||
e := New(Preset(0, 0), 0)
|
||||
if _, err := e.Read(nil); err != nil {
|
||||
t.Errorf("Read: error = %v", err)
|
||||
return
|
||||
}
|
||||
// the underlying implementation uses buffered io, so the outcome of this is nondeterministic;
|
||||
// that is not harmful however, so both outcomes are checked for here
|
||||
if err := e.Close(); err != nil &&
|
||||
(!errors.Is(err, syscall.ECANCELED) || !errors.Is(err, syscall.EBADF)) {
|
||||
t.Errorf("Close: error = %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkExport(b *testing.B) {
|
||||
buf := make([]byte, 8)
|
||||
const exportFlags = AllowMultiarch | AllowCAN | AllowBluetooth
|
||||
const presetFlags = PresetExt | PresetDenyNS | PresetDenyTTY | PresetDenyDevel | PresetLinux32
|
||||
var want = bpfExpected[bpfPreset{exportFlags, presetFlags}]
|
||||
|
||||
for b.Loop() {
|
||||
e := New(
|
||||
Preset(PresetExt|PresetDenyNS|PresetDenyTTY|PresetDenyDevel|PresetLinux32,
|
||||
AllowMultiarch|AllowCAN|AllowBluetooth),
|
||||
AllowMultiarch|AllowCAN|AllowBluetooth)
|
||||
if _, err := io.CopyBuffer(io.Discard, e, buf); err != nil {
|
||||
b.Fatalf("cannot export: %v", err)
|
||||
}
|
||||
if err := e.Close(); err != nil {
|
||||
b.Fatalf("cannot close exporter: %v", err)
|
||||
data, err := Export(Preset(presetFlags, exportFlags), exportFlags)
|
||||
|
||||
b.StopTimer()
|
||||
if err != nil {
|
||||
b.Fatalf("Export: error = %v", err)
|
||||
}
|
||||
if got := sha512.Sum512(data); got != want {
|
||||
b.Fatalf("Export: hash = %x, want %x", got, want)
|
||||
return
|
||||
}
|
||||
b.StartTimer()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
)
|
||||
|
||||
func Preset(presets bits.FilterPreset, flags ExportFlag) (rules []NativeRule) {
|
||||
allowedPersonality := PER_LINUX
|
||||
allowedPersonality := PersonaLinux
|
||||
if presets&bits.PresetLinux32 != 0 {
|
||||
allowedPersonality = PER_LINUX32
|
||||
allowedPersonality = PersonaLinux32
|
||||
}
|
||||
presetDevelFinal := presetDevel(ScmpDatum(allowedPersonality))
|
||||
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
package seccomp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/helper/proc"
|
||||
)
|
||||
|
||||
// New returns an inactive Encoder instance.
|
||||
func New(rules []NativeRule, flags ExportFlag) *Encoder { return &Encoder{newExporter(rules, flags)} }
|
||||
|
||||
// Load loads a filter into the kernel.
|
||||
func Load(rules []NativeRule, flags ExportFlag) error { return Export(-1, rules, flags) }
|
||||
|
||||
/*
|
||||
An Encoder writes a BPF program to an output stream.
|
||||
|
||||
Methods of Encoder are not safe for concurrent use.
|
||||
|
||||
An Encoder must not be copied after first use.
|
||||
*/
|
||||
type Encoder struct{ *exporter }
|
||||
|
||||
func (e *Encoder) Read(p []byte) (n int, err error) {
|
||||
if err = e.prepare(); err != nil {
|
||||
return
|
||||
}
|
||||
return e.r.Read(p)
|
||||
}
|
||||
|
||||
func (e *Encoder) Close() error {
|
||||
if e.r == nil {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
|
||||
// this hangs if the cgo thread fails to exit
|
||||
return errors.Join(e.closeWrite(), <-e.exportErr)
|
||||
}
|
||||
|
||||
// NewFile returns an instance of exporter implementing [proc.File].
|
||||
func NewFile(rules []NativeRule, flags ExportFlag) proc.File {
|
||||
return &File{rules: rules, flags: flags}
|
||||
}
|
||||
|
||||
// File implements [proc.File] and provides access to the read end of exporter pipe.
|
||||
type File struct {
|
||||
rules []NativeRule
|
||||
flags ExportFlag
|
||||
proc.BaseFile
|
||||
}
|
||||
|
||||
func (f *File) ErrCount() int { return 2 }
|
||||
func (f *File) Fulfill(ctx context.Context, dispatchErr func(error)) error {
|
||||
e := newExporter(f.rules, f.flags)
|
||||
if err := e.prepare(); err != nil {
|
||||
return err
|
||||
}
|
||||
f.Set(e.r)
|
||||
go func() {
|
||||
select {
|
||||
case err := <-e.exportErr:
|
||||
dispatchErr(nil)
|
||||
dispatchErr(err)
|
||||
case <-ctx.Done():
|
||||
dispatchErr(e.closeWrite())
|
||||
dispatchErr(<-e.exportErr)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// Package seccomp provides high level wrappers around libseccomp.
|
||||
package seccomp
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type exporter struct {
|
||||
rules []NativeRule
|
||||
flags ExportFlag
|
||||
r, w *os.File
|
||||
|
||||
prepareOnce sync.Once
|
||||
prepareErr error
|
||||
closeOnce sync.Once
|
||||
closeErr error
|
||||
exportErr <-chan error
|
||||
}
|
||||
|
||||
func (e *exporter) prepare() error {
|
||||
e.prepareOnce.Do(func() {
|
||||
if r, w, err := os.Pipe(); err != nil {
|
||||
e.prepareErr = err
|
||||
return
|
||||
} else {
|
||||
e.r, e.w = r, w
|
||||
}
|
||||
|
||||
ec := make(chan error, 1)
|
||||
go func(fd uintptr) {
|
||||
ec <- Export(int(fd), e.rules, e.flags)
|
||||
close(ec)
|
||||
_ = e.closeWrite()
|
||||
runtime.KeepAlive(e.w)
|
||||
}(e.w.Fd())
|
||||
e.exportErr = ec
|
||||
runtime.SetFinalizer(e, (*exporter).closeWrite)
|
||||
})
|
||||
return e.prepareErr
|
||||
}
|
||||
|
||||
func (e *exporter) closeWrite() error {
|
||||
e.closeOnce.Do(func() {
|
||||
if e.w == nil {
|
||||
panic("closeWrite called on invalid exporter")
|
||||
}
|
||||
e.closeErr = e.w.Close()
|
||||
|
||||
// no need for a finalizer anymore
|
||||
runtime.SetFinalizer(e, nil)
|
||||
})
|
||||
|
||||
return e.closeErr
|
||||
}
|
||||
|
||||
func newExporter(rules []NativeRule, flags ExportFlag) *exporter {
|
||||
return &exporter{rules: rules, flags: flags}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package seccomp_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container/seccomp"
|
||||
)
|
||||
|
||||
func TestLibraryError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
sample *seccomp.LibraryError
|
||||
want string
|
||||
wantIs bool
|
||||
compare error
|
||||
}{
|
||||
{
|
||||
"full",
|
||||
&seccomp.LibraryError{Prefix: "seccomp_export_bpf failed", Seccomp: syscall.ECANCELED, Errno: syscall.EBADF},
|
||||
"seccomp_export_bpf failed: operation canceled (bad file descriptor)",
|
||||
true,
|
||||
&seccomp.LibraryError{Prefix: "seccomp_export_bpf failed", Seccomp: syscall.ECANCELED, Errno: syscall.EBADF},
|
||||
},
|
||||
{
|
||||
"errno only",
|
||||
&seccomp.LibraryError{Prefix: "seccomp_init failed", Errno: syscall.ENOMEM},
|
||||
"seccomp_init failed: cannot allocate memory",
|
||||
false,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"seccomp only",
|
||||
&seccomp.LibraryError{Prefix: "internal libseccomp failure", Seccomp: syscall.EFAULT},
|
||||
"internal libseccomp failure: bad address",
|
||||
true,
|
||||
syscall.EFAULT,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if errors.Is(tc.sample, tc.compare) != tc.wantIs {
|
||||
t.Errorf("errors.Is(%#v, %#v) did not return %v",
|
||||
tc.sample, tc.compare, tc.wantIs)
|
||||
}
|
||||
|
||||
if got := tc.sample.Error(); got != tc.want {
|
||||
t.Errorf("Error: %q, want %q",
|
||||
got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
wantPanic := "invalid libseccomp error"
|
||||
defer func() {
|
||||
if r := recover(); r != wantPanic {
|
||||
t.Errorf("panic: %q, want %q", r, wantPanic)
|
||||
}
|
||||
}()
|
||||
runtime.KeepAlive(new(seccomp.LibraryError).Error())
|
||||
})
|
||||
}
|
||||
@@ -35,7 +35,7 @@ func (a argsWt) String() string {
|
||||
}
|
||||
|
||||
// NewCheckedArgs returns a checked null-terminated argument writer for a copy of args.
|
||||
func NewCheckedArgs(args []string) (wt io.WriterTo, err error) {
|
||||
func NewCheckedArgs(args ...string) (wt io.WriterTo, err error) {
|
||||
a := make(argsWt, len(args))
|
||||
for i, arg := range args {
|
||||
a[i], err = syscall.ByteSliceFromString(arg)
|
||||
@@ -49,8 +49,8 @@ func NewCheckedArgs(args []string) (wt io.WriterTo, err error) {
|
||||
|
||||
// MustNewCheckedArgs returns a checked null-terminated argument writer for a copy of args.
|
||||
// If s contains a NUL byte this function panics instead of returning an error.
|
||||
func MustNewCheckedArgs(args []string) io.WriterTo {
|
||||
a, err := NewCheckedArgs(args)
|
||||
func MustNewCheckedArgs(args ...string) io.WriterTo {
|
||||
a, err := NewCheckedArgs(args...)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestNewCheckedArgs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"\x00"}
|
||||
if _, err := helper.NewCheckedArgs(args); !errors.Is(err, syscall.EINVAL) {
|
||||
if _, err := helper.NewCheckedArgs(args...); !errors.Is(err, syscall.EINVAL) {
|
||||
t.Errorf("NewCheckedArgs: error = %v, wantErr %v", err, syscall.EINVAL)
|
||||
}
|
||||
|
||||
@@ -36,6 +36,6 @@ func TestNewCheckedArgs(t *testing.T) {
|
||||
t.Errorf("MustNewCheckedArgs: panic = %v, wantPanic %v", r, wantPanic)
|
||||
}
|
||||
}()
|
||||
helper.MustNewCheckedArgs(badPayload)
|
||||
helper.MustNewCheckedArgs(badPayload...)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ var (
|
||||
}
|
||||
|
||||
wantPayload = strings.Join(wantArgs, "\x00") + "\x00"
|
||||
argsWt = helper.MustNewCheckedArgs(wantArgs)
|
||||
argsWt = helper.MustNewCheckedArgs(wantArgs...)
|
||||
)
|
||||
|
||||
func argF(argsFd, statFd int) []string {
|
||||
|
||||
@@ -3,37 +3,12 @@ package hst
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"hakurei.app/container/check"
|
||||
)
|
||||
|
||||
// PrivateTmp is a private writable path in a hakurei container.
|
||||
const PrivateTmp = "/.hakurei"
|
||||
|
||||
// AbsPrivateTmp is a [check.Absolute] representation of [PrivateTmp].
|
||||
var AbsPrivateTmp = check.MustAbs(PrivateTmp)
|
||||
|
||||
const (
|
||||
// WaitDelayDefault is used when WaitDelay has its zero value.
|
||||
WaitDelayDefault = 5 * time.Second
|
||||
// WaitDelayMax is used if WaitDelay exceeds its value.
|
||||
WaitDelayMax = 30 * time.Second
|
||||
|
||||
// IdentityMin is the minimum value of [Config.Identity]. This is enforced by cmd/hsu.
|
||||
IdentityMin = 0
|
||||
// IdentityMax is the maximum value of [Config.Identity]. This is enforced by cmd/hsu.
|
||||
IdentityMax = 9999
|
||||
|
||||
// ShimExitRequest is returned when the priv side process requests shim exit.
|
||||
ShimExitRequest = 254
|
||||
// ShimExitOrphan is returned when the shim is orphaned before priv side delivers a signal.
|
||||
ShimExitOrphan = 3
|
||||
)
|
||||
|
||||
type (
|
||||
// Config configures an application container, implemented in internal/app.
|
||||
Config struct {
|
||||
type Config struct {
|
||||
// Reverse-DNS style configured arbitrary identifier string.
|
||||
// Passed to wayland security-context-v1 and used as part of defaults in dbus session proxy.
|
||||
ID string `json:"id"`
|
||||
@@ -51,8 +26,8 @@ type (
|
||||
// and the bare socket is made available to the container.
|
||||
DirectWayland bool `json:"direct_wayland,omitempty"`
|
||||
|
||||
// Extra acl update ops to perform before setuid.
|
||||
ExtraPerms []*ExtraPermConfig `json:"extra_perms,omitempty"`
|
||||
// Extra acl updates to perform before setuid.
|
||||
ExtraPerms []ExtraPermConfig `json:"extra_perms,omitempty"`
|
||||
|
||||
// Numerical application id, passed to hsu, used to derive init user namespace credentials.
|
||||
Identity int `json:"identity"`
|
||||
@@ -63,63 +38,6 @@ type (
|
||||
Container *ContainerConfig `json:"container"`
|
||||
}
|
||||
|
||||
// ContainerConfig describes the container configuration to be applied to an underlying [container].
|
||||
ContainerConfig struct {
|
||||
// Container UTS namespace hostname.
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
|
||||
// Duration in nanoseconds to wait for after interrupting the initial process.
|
||||
// Defaults to [WaitDelayDefault] if zero, or [WaitDelayMax] if greater than [WaitDelayMax].
|
||||
// Values lesser than zero is equivalent to zero, bypassing [WaitDelayDefault].
|
||||
WaitDelay time.Duration `json:"wait_delay,omitempty"`
|
||||
|
||||
// Emit Flatpak-compatible seccomp filter programs.
|
||||
SeccompCompat bool `json:"seccomp_compat,omitempty"`
|
||||
// Allow ptrace and friends.
|
||||
Devel bool `json:"devel,omitempty"`
|
||||
// Allow userns creation and container setup syscalls.
|
||||
Userns bool `json:"userns,omitempty"`
|
||||
// Share host net namespace.
|
||||
HostNet bool `json:"host_net,omitempty"`
|
||||
// Share abstract unix socket scope.
|
||||
HostAbstract bool `json:"host_abstract,omitempty"`
|
||||
// Allow dangerous terminal I/O (faking input).
|
||||
Tty bool `json:"tty,omitempty"`
|
||||
// Allow multiarch.
|
||||
Multiarch bool `json:"multiarch,omitempty"`
|
||||
|
||||
// Initial process environment variables.
|
||||
Env map[string]string `json:"env"`
|
||||
|
||||
/* Map target user uid to privileged user uid in the container user namespace.
|
||||
|
||||
Some programs fail to connect to dbus session running as a different uid,
|
||||
this option works around it by mapping priv-side caller uid in container. */
|
||||
MapRealUID bool `json:"map_real_uid"`
|
||||
|
||||
// Mount /dev/ from the init mount namespace as-is in the container mount namespace.
|
||||
Device bool `json:"device,omitempty"`
|
||||
|
||||
/* Container mount points.
|
||||
|
||||
If the first element targets /, it is inserted early and excluded from path hiding. */
|
||||
Filesystem []FilesystemConfigJSON `json:"filesystem"`
|
||||
|
||||
// String used as the username of the emulated user, validated against the default NAME_REGEX from adduser.
|
||||
// Defaults to passwd name of target uid or chronos.
|
||||
Username string `json:"username,omitempty"`
|
||||
// Pathname of shell in the container filesystem to use for the emulated user.
|
||||
Shell *check.Absolute `json:"shell"`
|
||||
// Directory in the container filesystem to enter and use as the home directory of the emulated user.
|
||||
Home *check.Absolute `json:"home"`
|
||||
|
||||
// Pathname to executable file in the container filesystem.
|
||||
Path *check.Absolute `json:"path,omitempty"`
|
||||
// Final args passed to the initial program.
|
||||
Args []string `json:"args"`
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrConfigNull is returned by [Config.Validate] for an invalid configuration that contains a null value for any
|
||||
// field that must not be null.
|
||||
@@ -168,15 +86,21 @@ func (config *Config) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtraPermConfig describes an acl update op.
|
||||
// ExtraPermConfig describes an acl update to perform before setuid.
|
||||
type ExtraPermConfig struct {
|
||||
// Whether to create Path as a directory if it does not exist.
|
||||
Ensure bool `json:"ensure,omitempty"`
|
||||
// Pathname to act on.
|
||||
Path *check.Absolute `json:"path"`
|
||||
// Whether to set ACL_READ for the target user.
|
||||
Read bool `json:"r,omitempty"`
|
||||
// Whether to set ACL_WRITE for the target user.
|
||||
Write bool `json:"w,omitempty"`
|
||||
// Whether to set ACL_EXECUTE for the target user.
|
||||
Execute bool `json:"x,omitempty"`
|
||||
}
|
||||
|
||||
// String returns a checked string representation of [ExtraPermConfig].
|
||||
func (e *ExtraPermConfig) String() string {
|
||||
if e == nil || e.Path == nil {
|
||||
return "<invalid>"
|
||||
|
||||
189
hst/container.go
Normal file
189
hst/container.go
Normal file
@@ -0,0 +1,189 @@
|
||||
package hst
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"hakurei.app/container/check"
|
||||
)
|
||||
|
||||
// PrivateTmp is a private writable path in a hakurei container.
|
||||
const PrivateTmp = "/.hakurei"
|
||||
|
||||
// AbsPrivateTmp is a [check.Absolute] representation of [PrivateTmp].
|
||||
var AbsPrivateTmp = check.MustAbs(PrivateTmp)
|
||||
|
||||
const (
|
||||
// WaitDelayDefault is used when WaitDelay has its zero value.
|
||||
WaitDelayDefault = 5 * time.Second
|
||||
// WaitDelayMax is used if WaitDelay exceeds its value.
|
||||
WaitDelayMax = 30 * time.Second
|
||||
|
||||
// IdentityMin is the minimum value of [Config.Identity]. This is enforced by cmd/hsu.
|
||||
IdentityMin = 0
|
||||
// IdentityMax is the maximum value of [Config.Identity]. This is enforced by cmd/hsu.
|
||||
IdentityMax = 9999
|
||||
|
||||
// ShimExitRequest is returned when the priv side process requests shim exit.
|
||||
ShimExitRequest = 254
|
||||
// ShimExitOrphan is returned when the shim is orphaned before priv side delivers a signal.
|
||||
ShimExitOrphan = 3
|
||||
)
|
||||
|
||||
const (
|
||||
// FMultiarch unblocks syscalls required for multiarch to work on applicable targets.
|
||||
FMultiarch uintptr = 1 << iota
|
||||
|
||||
// FSeccompCompat causes emitted seccomp filter programs to be identical to Flatpak.
|
||||
FSeccompCompat
|
||||
// FDevel unblocks ptrace and friends.
|
||||
FDevel
|
||||
// FUserns unblocks userns creation and container setup syscalls.
|
||||
FUserns
|
||||
// FHostNet skips net namespace creation.
|
||||
FHostNet
|
||||
// FHostAbstract skips setting up abstract unix socket scope.
|
||||
FHostAbstract
|
||||
// FTty unblocks dangerous terminal I/O (faking input).
|
||||
FTty
|
||||
|
||||
// FMapRealUID maps the target user uid to the privileged user uid in the container user namespace.
|
||||
// Some programs fail to connect to dbus session running as a different uid,
|
||||
// this option works around it by mapping priv-side caller uid in container.
|
||||
FMapRealUID
|
||||
|
||||
// FDevice mount /dev/ from the init mount namespace as-is in the container mount namespace.
|
||||
FDevice
|
||||
|
||||
fMax
|
||||
|
||||
// FAll is [ContainerConfig.Flags] with all currently defined bits set.
|
||||
FAll = fMax - 1
|
||||
)
|
||||
|
||||
// ContainerConfig describes the container configuration to be applied to an underlying [container].
|
||||
type ContainerConfig struct {
|
||||
// Container UTS namespace hostname.
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
|
||||
// Duration in nanoseconds to wait for after interrupting the initial process.
|
||||
// Defaults to [WaitDelayDefault] if zero, or [WaitDelayMax] if greater than [WaitDelayMax].
|
||||
// Values lesser than zero is equivalent to zero, bypassing [WaitDelayDefault].
|
||||
WaitDelay time.Duration `json:"wait_delay,omitempty"`
|
||||
|
||||
// Initial process environment variables.
|
||||
Env map[string]string `json:"env"`
|
||||
|
||||
/* Container mount points.
|
||||
|
||||
If the first element targets /, it is inserted early and excluded from path hiding. */
|
||||
Filesystem []FilesystemConfigJSON `json:"filesystem"`
|
||||
|
||||
// String used as the username of the emulated user, validated against the default NAME_REGEX from adduser.
|
||||
// Defaults to passwd name of target uid or chronos.
|
||||
Username string `json:"username,omitempty"`
|
||||
// Pathname of shell in the container filesystem to use for the emulated user.
|
||||
Shell *check.Absolute `json:"shell"`
|
||||
// Directory in the container filesystem to enter and use as the home directory of the emulated user.
|
||||
Home *check.Absolute `json:"home"`
|
||||
|
||||
// Pathname to executable file in the container filesystem.
|
||||
Path *check.Absolute `json:"path,omitempty"`
|
||||
// Final args passed to the initial program.
|
||||
Args []string `json:"args"`
|
||||
|
||||
// Flags holds boolean options of [ContainerConfig].
|
||||
Flags uintptr `json:"-"`
|
||||
}
|
||||
|
||||
// ContainerConfigF is [ContainerConfig] stripped of its methods.
|
||||
// The [ContainerConfig.Flags] field does not survive a [json] round trip.
|
||||
type ContainerConfigF ContainerConfig
|
||||
|
||||
// containerConfigJSON is the [json] representation of [ContainerConfig].
|
||||
type containerConfigJSON = struct {
|
||||
*ContainerConfigF
|
||||
|
||||
// Corresponds to [FSeccompCompat].
|
||||
SeccompCompat bool `json:"seccomp_compat,omitempty"`
|
||||
// Corresponds to [FDevel].
|
||||
Devel bool `json:"devel,omitempty"`
|
||||
// Corresponds to [FUserns].
|
||||
Userns bool `json:"userns,omitempty"`
|
||||
// Corresponds to [FHostNet].
|
||||
HostNet bool `json:"host_net,omitempty"`
|
||||
// Corresponds to [FHostAbstract].
|
||||
HostAbstract bool `json:"host_abstract,omitempty"`
|
||||
// Corresponds to [FTty].
|
||||
Tty bool `json:"tty,omitempty"`
|
||||
|
||||
// Corresponds to [FMultiarch].
|
||||
Multiarch bool `json:"multiarch,omitempty"`
|
||||
|
||||
// Corresponds to [FMapRealUID].
|
||||
MapRealUID bool `json:"map_real_uid"`
|
||||
|
||||
// Corresponds to [FDevice].
|
||||
Device bool `json:"device,omitempty"`
|
||||
}
|
||||
|
||||
func (c *ContainerConfig) MarshalJSON() ([]byte, error) {
|
||||
if c == nil {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
return json.Marshal(&containerConfigJSON{
|
||||
ContainerConfigF: (*ContainerConfigF)(c),
|
||||
|
||||
SeccompCompat: c.Flags&FSeccompCompat != 0,
|
||||
Devel: c.Flags&FDevel != 0,
|
||||
Userns: c.Flags&FUserns != 0,
|
||||
HostNet: c.Flags&FHostNet != 0,
|
||||
HostAbstract: c.Flags&FHostAbstract != 0,
|
||||
Tty: c.Flags&FTty != 0,
|
||||
Multiarch: c.Flags&FMultiarch != 0,
|
||||
MapRealUID: c.Flags&FMapRealUID != 0,
|
||||
Device: c.Flags&FDevice != 0,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ContainerConfig) UnmarshalJSON(data []byte) error {
|
||||
if c == nil {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
|
||||
v := new(containerConfigJSON)
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*c = *(*ContainerConfig)(v.ContainerConfigF)
|
||||
if v.SeccompCompat {
|
||||
c.Flags |= FSeccompCompat
|
||||
}
|
||||
if v.Devel {
|
||||
c.Flags |= FDevel
|
||||
}
|
||||
if v.Userns {
|
||||
c.Flags |= FUserns
|
||||
}
|
||||
if v.HostNet {
|
||||
c.Flags |= FHostNet
|
||||
}
|
||||
if v.HostAbstract {
|
||||
c.Flags |= FHostAbstract
|
||||
}
|
||||
if v.Tty {
|
||||
c.Flags |= FTty
|
||||
}
|
||||
if v.Multiarch {
|
||||
c.Flags |= FMultiarch
|
||||
}
|
||||
if v.MapRealUID {
|
||||
c.Flags |= FMapRealUID
|
||||
}
|
||||
if v.Device {
|
||||
c.Flags |= FDevice
|
||||
}
|
||||
return nil
|
||||
}
|
||||
76
hst/container_test.go
Normal file
76
hst/container_test.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package hst_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
func TestContainerConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
c *hst.ContainerConfig
|
||||
data string
|
||||
}{
|
||||
{"nil", nil, "null"},
|
||||
{"zero", new(hst.ContainerConfig),
|
||||
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"map_real_uid":false}`},
|
||||
{"seccomp compat", &hst.ContainerConfig{Flags: hst.FSeccompCompat},
|
||||
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"seccomp_compat":true,"map_real_uid":false}`},
|
||||
{"hostnet hostabstract", &hst.ContainerConfig{Flags: hst.FHostNet | hst.FHostAbstract},
|
||||
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"host_net":true,"host_abstract":true,"map_real_uid":false}`},
|
||||
{"hostnet hostabstract mapuid", &hst.ContainerConfig{Flags: hst.FHostNet | hst.FHostAbstract | hst.FMapRealUID},
|
||||
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"host_net":true,"host_abstract":true,"map_real_uid":true}`},
|
||||
{"all", &hst.ContainerConfig{Flags: hst.FAll},
|
||||
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"seccomp_compat":true,"devel":true,"userns":true,"host_net":true,"host_abstract":true,"tty":true,"multiarch":true,"map_real_uid":true,"device":true}`},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("marshal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got, err := json.Marshal(tc.c); err != nil {
|
||||
t.Fatalf("Marshal: error = %v", err)
|
||||
} else if string(got) != tc.data {
|
||||
t.Errorf("Marshal:\n%s, want\n%s", string(got), tc.data)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unmarshal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
{
|
||||
got := new(hst.ContainerConfig)
|
||||
if err := json.Unmarshal([]byte(tc.data), &got); err != nil {
|
||||
t.Fatalf("Unmarshal: error = %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, tc.c) {
|
||||
t.Errorf("Unmarshal: %v, want %v", got, tc.c)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("passthrough", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if _, err := (*hst.ContainerConfig)(nil).MarshalJSON(); !errors.Is(err, syscall.EINVAL) {
|
||||
t.Errorf("MarshalJSON: error = %v", err)
|
||||
}
|
||||
if err := (*hst.ContainerConfig)(nil).UnmarshalJSON(nil); !errors.Is(err, syscall.EINVAL) {
|
||||
t.Errorf("UnmarshalJSON: error = %v", err)
|
||||
}
|
||||
if err := new(hst.ContainerConfig).UnmarshalJSON([]byte{}); err == nil {
|
||||
t.Errorf("UnmarshalJSON: error = %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
15
hst/hst.go
15
hst/hst.go
@@ -3,6 +3,7 @@ package hst
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
@@ -87,7 +88,7 @@ func Template() *Config {
|
||||
},
|
||||
DirectWayland: false,
|
||||
|
||||
ExtraPerms: []*ExtraPermConfig{
|
||||
ExtraPerms: []ExtraPermConfig{
|
||||
{Path: fhs.AbsVarLib.Append("hakurei/u0"), Ensure: true, Execute: true},
|
||||
{Path: fhs.AbsVarLib.Append("hakurei/u0/org.chromium.Chromium"), Read: true, Write: true, Execute: true},
|
||||
},
|
||||
@@ -97,16 +98,7 @@ func Template() *Config {
|
||||
|
||||
Container: &ContainerConfig{
|
||||
Hostname: "localhost",
|
||||
Devel: true,
|
||||
Userns: true,
|
||||
HostNet: true,
|
||||
HostAbstract: true,
|
||||
Device: true,
|
||||
WaitDelay: -1,
|
||||
SeccompCompat: true,
|
||||
Tty: true,
|
||||
Multiarch: true,
|
||||
MapRealUID: true,
|
||||
// example API credentials pulled from Google Chrome
|
||||
// DO NOT USE THESE IN A REAL BROWSER
|
||||
Env: map[string]string{
|
||||
@@ -143,6 +135,9 @@ func Template() *Config {
|
||||
"--enable-features=UseOzonePlatform",
|
||||
"--ozone-platform=wayland",
|
||||
},
|
||||
|
||||
// Set all bits here so new flags trip the template test.
|
||||
Flags: math.MaxUint,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
@@ -164,20 +165,11 @@ func TestTemplate(t *testing.T) {
|
||||
"container": {
|
||||
"hostname": "localhost",
|
||||
"wait_delay": -1,
|
||||
"seccomp_compat": true,
|
||||
"devel": true,
|
||||
"userns": true,
|
||||
"host_net": true,
|
||||
"host_abstract": true,
|
||||
"tty": true,
|
||||
"multiarch": true,
|
||||
"env": {
|
||||
"GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
|
||||
"GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
|
||||
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
|
||||
},
|
||||
"map_real_uid": true,
|
||||
"device": true,
|
||||
"filesystem": [
|
||||
{
|
||||
"type": "bind",
|
||||
@@ -243,14 +235,41 @@ func TestTemplate(t *testing.T) {
|
||||
"--disable-smooth-scrolling",
|
||||
"--enable-features=UseOzonePlatform",
|
||||
"--ozone-platform=wayland"
|
||||
]
|
||||
],
|
||||
"seccomp_compat": true,
|
||||
"devel": true,
|
||||
"userns": true,
|
||||
"host_net": true,
|
||||
"host_abstract": true,
|
||||
"tty": true,
|
||||
"multiarch": true,
|
||||
"map_real_uid": true,
|
||||
"device": true
|
||||
}
|
||||
}`
|
||||
|
||||
t.Run("marshal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if p, err := json.MarshalIndent(hst.Template(), "", "\t"); err != nil {
|
||||
t.Fatalf("cannot marshal: %v", err)
|
||||
} else if s := string(p); s != want {
|
||||
t.Fatalf("Template:\n%s\nwant:\n%s",
|
||||
s, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unmarshal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var got *hst.Config
|
||||
if err := json.Unmarshal([]byte(want), &got); err != nil {
|
||||
t.Fatalf("Unmarshal: error = %v", err)
|
||||
}
|
||||
|
||||
wantVal := hst.Template()
|
||||
wantVal.Container.Flags = hst.FAll
|
||||
if !reflect.DeepEqual(got, wantVal) {
|
||||
t.Fatalf("Unmarshal: %#v, want %#v", got, wantVal)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/acl"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
func TestApp(t *testing.T) {
|
||||
@@ -43,8 +44,6 @@ func TestApp(t *testing.T) {
|
||||
{
|
||||
"nixos permissive defaults no enablements", new(stubNixOS),
|
||||
&hst.Config{Container: &hst.ContainerConfig{
|
||||
Userns: true, HostNet: true, HostAbstract: true, Tty: true,
|
||||
|
||||
Filesystem: []hst.FilesystemConfigJSON{
|
||||
{FilesystemConfig: &hst.FSBind{
|
||||
Target: fhs.AbsRoot,
|
||||
@@ -70,6 +69,8 @@ func TestApp(t *testing.T) {
|
||||
|
||||
Path: m("/run/current-system/sw/bin/zsh"),
|
||||
Args: []string{"/run/current-system/sw/bin/zsh"},
|
||||
|
||||
Flags: hst.FUserns | hst.FHostNet | hst.FHostAbstract | hst.FTty,
|
||||
}},
|
||||
state.ID{
|
||||
0x4a, 0x45, 0x0b, 0x65,
|
||||
@@ -161,8 +162,6 @@ func TestApp(t *testing.T) {
|
||||
Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPulse),
|
||||
|
||||
Container: &hst.ContainerConfig{
|
||||
Userns: true, HostNet: true, HostAbstract: true, Tty: true,
|
||||
|
||||
Filesystem: []hst.FilesystemConfigJSON{
|
||||
{FilesystemConfig: &hst.FSBind{
|
||||
Target: fhs.AbsRoot,
|
||||
@@ -193,6 +192,8 @@ func TestApp(t *testing.T) {
|
||||
|
||||
Path: m("/run/current-system/sw/bin/zsh"),
|
||||
Args: []string{"zsh", "-c", "exec chromium "},
|
||||
|
||||
Flags: hst.FUserns | hst.FHostNet | hst.FHostAbstract | hst.FTty,
|
||||
},
|
||||
},
|
||||
state.ID{
|
||||
@@ -213,7 +214,7 @@ func TestApp(t *testing.T) {
|
||||
Ensure(m("/run/user/1971"), 0700).UpdatePermType(system.User, m("/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, m("/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c"), 0700).UpdatePermType(system.Process, m("/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c"), acl.Execute).
|
||||
Link(m("/run/user/1971/pulse/native"), m("/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c/pulse")).
|
||||
MustProxyDBus(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus"), &hst.BusConfig{
|
||||
MustProxyDBus(&hst.BusConfig{
|
||||
Talk: []string{
|
||||
"org.freedesktop.Notifications",
|
||||
"org.freedesktop.FileManager1",
|
||||
@@ -235,13 +236,19 @@ func TestApp(t *testing.T) {
|
||||
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*",
|
||||
},
|
||||
Filter: true,
|
||||
}, m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket"), &hst.BusConfig{
|
||||
}, &hst.BusConfig{
|
||||
Talk: []string{
|
||||
"org.bluez",
|
||||
"org.freedesktop.Avahi",
|
||||
"org.freedesktop.UPower",
|
||||
},
|
||||
Filter: true,
|
||||
}, dbus.ProxyPair{
|
||||
"unix:path=/run/user/1971/bus",
|
||||
"/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus",
|
||||
}, dbus.ProxyPair{
|
||||
"unix:path=/var/run/dbus/system_bus_socket",
|
||||
"/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket",
|
||||
}).
|
||||
UpdatePerm(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus"), acl.Read, acl.Write).
|
||||
UpdatePerm(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket"), acl.Read, acl.Write),
|
||||
@@ -301,7 +308,7 @@ func TestApp(t *testing.T) {
|
||||
ID: "org.chromium.Chromium",
|
||||
Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPulse),
|
||||
Container: &hst.ContainerConfig{
|
||||
Userns: true, HostNet: true, MapRealUID: true, Env: nil,
|
||||
Env: nil,
|
||||
Filesystem: []hst.FilesystemConfigJSON{
|
||||
f(&hst.FSBind{Source: m("/bin")}),
|
||||
f(&hst.FSBind{Source: m("/usr/bin/")}),
|
||||
@@ -323,6 +330,8 @@ func TestApp(t *testing.T) {
|
||||
Home: m("/var/lib/persist/module/hakurei/0/1"),
|
||||
|
||||
Path: m("/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"),
|
||||
|
||||
Flags: hst.FUserns | hst.FHostNet | hst.FMapRealUID,
|
||||
},
|
||||
SystemBus: &hst.BusConfig{
|
||||
Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
|
||||
@@ -364,7 +373,7 @@ func TestApp(t *testing.T) {
|
||||
Ephemeral(system.Process, m("/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1"), 0700).UpdatePermType(system.Process, m("/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1"), acl.Execute).
|
||||
Link(m("/run/user/1971/pulse/native"), m("/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse")).
|
||||
Ephemeral(system.Process, m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1"), 0711).
|
||||
MustProxyDBus(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus"), &hst.BusConfig{
|
||||
MustProxyDBus(&hst.BusConfig{
|
||||
Talk: []string{
|
||||
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
|
||||
"org.freedesktop.ScreenSaver", "org.freedesktop.secrets",
|
||||
@@ -377,13 +386,19 @@ func TestApp(t *testing.T) {
|
||||
},
|
||||
Call: map[string]string{}, Broadcast: map[string]string{},
|
||||
Filter: true,
|
||||
}, m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket"), &hst.BusConfig{
|
||||
}, &hst.BusConfig{
|
||||
Talk: []string{
|
||||
"org.bluez",
|
||||
"org.freedesktop.Avahi",
|
||||
"org.freedesktop.UPower",
|
||||
},
|
||||
Filter: true,
|
||||
}, dbus.ProxyPair{
|
||||
"unix:path=/run/user/1971/bus",
|
||||
"/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus",
|
||||
}, dbus.ProxyPair{
|
||||
"unix:path=/var/run/dbus/system_bus_socket",
|
||||
"/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket",
|
||||
}).
|
||||
UpdatePerm(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus"), acl.Read, acl.Write).
|
||||
UpdatePerm(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket"), acl.Read, acl.Write),
|
||||
@@ -739,6 +754,10 @@ func (k *stubNixOS) overflowGid(message.Msg) int { return 65534 }
|
||||
|
||||
func (k *stubNixOS) mustHsuPath() *check.Absolute { return m("/proc/nonexistent/hsu") }
|
||||
|
||||
func (k *stubNixOS) dbusAddress() (string, string) {
|
||||
return "unix:path=/run/user/1971/bus", "unix:path=/var/run/dbus/system_bus_socket"
|
||||
}
|
||||
|
||||
func (k *stubNixOS) fatalf(format string, v ...any) { panic(fmt.Sprintf(format, v...)) }
|
||||
|
||||
func (k *stubNixOS) isVerbose() bool { return true }
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
// osFile represents [os.File].
|
||||
@@ -63,6 +64,9 @@ type syscallDispatcher interface {
|
||||
// mustHsuPath provides [internal.MustHsuPath].
|
||||
mustHsuPath() *check.Absolute
|
||||
|
||||
// dbusAddress provides [dbus.Address].
|
||||
dbusAddress() (session, system string)
|
||||
|
||||
// fatalf provides [log.Fatalf].
|
||||
fatalf(format string, v ...any)
|
||||
}
|
||||
@@ -99,4 +103,6 @@ func (direct) overflowGid(msg message.Msg) int { return container.OverflowGid(ms
|
||||
|
||||
func (direct) mustHsuPath() *check.Absolute { return internal.MustHsuPath() }
|
||||
|
||||
func (k direct) dbusAddress() (session, system string) { return dbus.Address() }
|
||||
|
||||
func (direct) fatalf(format string, v ...any) { log.Fatalf(format, v...) }
|
||||
|
||||
@@ -97,7 +97,7 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
|
||||
if err := s.populateLocal(k, k); err != nil {
|
||||
t.Fatalf("populateLocal: error = %v", err)
|
||||
}
|
||||
stateSys := s.newSys(config, newI())
|
||||
stateSys := s.newSys(config, system.New(panicMsgContext{}, k, checkExpectUid))
|
||||
if tc.pStateSys != nil {
|
||||
tc.pStateSys(stateSys)
|
||||
}
|
||||
@@ -218,6 +218,12 @@ func (k *kstub) mustHsuPath() *check.Absolute {
|
||||
return k.Expects("mustHsuPath").Ret.(*check.Absolute)
|
||||
}
|
||||
|
||||
func (k *kstub) dbusAddress() (session, system string) {
|
||||
k.Helper()
|
||||
ret := k.Expects("dbusAddress").Ret.([2]string)
|
||||
return ret[0], ret[1]
|
||||
}
|
||||
|
||||
func (k *kstub) GetLogger() *log.Logger { panic("unreachable") }
|
||||
|
||||
func (k *kstub) IsVerbose() bool { k.Helper(); return k.Expects("isVerbose").Ret.(bool) }
|
||||
@@ -320,4 +326,5 @@ func (panicDispatcher) cmdOutput(*exec.Cmd) ([]byte, error) { panic("unreachab
|
||||
func (panicDispatcher) overflowUid(message.Msg) int { panic("unreachable") }
|
||||
func (panicDispatcher) overflowGid(message.Msg) int { panic("unreachable") }
|
||||
func (panicDispatcher) mustHsuPath() *check.Absolute { panic("unreachable") }
|
||||
func (panicDispatcher) dbusAddress() (string, string) { panic("unreachable") }
|
||||
func (panicDispatcher) fatalf(string, ...any) { panic("unreachable") }
|
||||
|
||||
@@ -94,7 +94,7 @@ func newOutcomeState(k syscallDispatcher, msg message.Msg, id *state.ID, config
|
||||
s.Shim.WaitDelay = s.Container.WaitDelay
|
||||
}
|
||||
|
||||
if s.Container.MapRealUID {
|
||||
if s.Container.Flags&hst.FMapRealUID != 0 {
|
||||
s.Mapuid, s.Mapgid = k.getuid(), k.getgid()
|
||||
} else {
|
||||
s.Mapuid, s.Mapgid = k.overflowUid(msg), k.overflowGid(msg)
|
||||
@@ -156,7 +156,7 @@ type outcomeStateSys struct {
|
||||
// Copied from [hst.Config]. Safe for read by spWaylandOp.toSystem only.
|
||||
directWayland bool
|
||||
// Copied header from [hst.Config]. Safe for read by spFinalOp.toSystem only.
|
||||
extraPerms []*hst.ExtraPermConfig
|
||||
extraPerms []hst.ExtraPermConfig
|
||||
// Copied address from [hst.Config]. Safe for read by spDBusOp.toSystem only.
|
||||
sessionBus, systemBus *hst.BusConfig
|
||||
|
||||
|
||||
@@ -48,9 +48,9 @@ func (s *spParamsOp) toContainer(state *outcomeStateParams) error {
|
||||
const preallocateOpsCount = 1 << 5
|
||||
|
||||
state.params.Hostname = state.Container.Hostname
|
||||
state.params.RetainSession = state.Container.Tty
|
||||
state.params.HostNet = state.Container.HostNet
|
||||
state.params.HostAbstract = state.Container.HostAbstract
|
||||
state.params.RetainSession = state.Container.Flags&hst.FTty != 0
|
||||
state.params.HostNet = state.Container.Flags&hst.FHostNet != 0
|
||||
state.params.HostAbstract = state.Container.Flags&hst.FHostAbstract != 0
|
||||
|
||||
if state.Container.Path == nil {
|
||||
return newWithMessage("invalid program path")
|
||||
@@ -67,24 +67,24 @@ func (s *spParamsOp) toContainer(state *outcomeStateParams) error {
|
||||
// this behaviour is implemented in the shim
|
||||
state.params.ForwardCancel = state.Shim.WaitDelay > 0
|
||||
|
||||
if state.Container.Multiarch {
|
||||
if state.Container.Flags&hst.FMultiarch != 0 {
|
||||
state.params.SeccompFlags |= seccomp.AllowMultiarch
|
||||
}
|
||||
|
||||
if !state.Container.SeccompCompat {
|
||||
if state.Container.Flags&hst.FSeccompCompat == 0 {
|
||||
state.params.SeccompPresets |= bits.PresetExt
|
||||
}
|
||||
if !state.Container.Devel {
|
||||
if state.Container.Flags&hst.FDevel == 0 {
|
||||
state.params.SeccompPresets |= bits.PresetDenyDevel
|
||||
}
|
||||
if !state.Container.Userns {
|
||||
if state.Container.Flags&hst.FUserns == 0 {
|
||||
state.params.SeccompPresets |= bits.PresetDenyNS
|
||||
}
|
||||
if !state.Container.Tty {
|
||||
if state.Container.Flags&hst.FTty == 0 {
|
||||
state.params.SeccompPresets |= bits.PresetDenyTTY
|
||||
}
|
||||
|
||||
if state.Container.MapRealUID {
|
||||
if state.Container.Flags&hst.FMapRealUID != 0 {
|
||||
state.params.Uid = state.Mapuid
|
||||
state.params.Gid = state.Mapgid
|
||||
}
|
||||
@@ -106,7 +106,7 @@ func (s *spParamsOp) toContainer(state *outcomeStateParams) error {
|
||||
state.params.
|
||||
Proc(fhs.AbsProc).
|
||||
Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755)
|
||||
if !state.Container.Device {
|
||||
if state.Container.Flags&hst.FDevice == 0 {
|
||||
state.params.DevWritable(fhs.AbsDev, true)
|
||||
} else {
|
||||
state.params.Bind(fhs.AbsDev, fhs.AbsDev, bits.BindWritable|bits.BindDevice)
|
||||
@@ -275,7 +275,7 @@ func (s *spFilesystemOp) toContainer(state *outcomeStateParams) error {
|
||||
}
|
||||
|
||||
// no more configured paths beyond this point
|
||||
if !state.Container.Device {
|
||||
if state.Container.Flags&hst.FDevice == 0 {
|
||||
state.params.Remount(fhs.AbsDev, syscall.MS_RDONLY)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -51,12 +51,7 @@ func TestSpParamsOp(t *testing.T) {
|
||||
}, func() *hst.Config {
|
||||
c := hst.Template()
|
||||
c.Container.Args = nil
|
||||
c.Container.Multiarch = false
|
||||
c.Container.SeccompCompat = false
|
||||
c.Container.Devel = false
|
||||
c.Container.Userns = false
|
||||
c.Container.Tty = false
|
||||
c.Container.Device = false
|
||||
c.Container.Flags = hst.FHostNet | hst.FHostAbstract | hst.FMapRealUID
|
||||
return c
|
||||
}, nil, []stub.Call{
|
||||
call("lookupEnv", stub.ExpectArgs{"TERM"}, "xterm", nil),
|
||||
@@ -65,8 +60,8 @@ func TestSpParamsOp(t *testing.T) {
|
||||
// this op configures the container state and does not make calls during toContainer
|
||||
}, &container.Params{
|
||||
Hostname: config.Container.Hostname,
|
||||
HostNet: config.Container.HostNet,
|
||||
HostAbstract: config.Container.HostAbstract,
|
||||
HostNet: true,
|
||||
HostAbstract: true,
|
||||
Path: config.Container.Path,
|
||||
Args: []string{config.Container.Path.String()},
|
||||
SeccompPresets: bits.PresetExt | bits.PresetDenyDevel | bits.PresetDenyNS | bits.PresetDenyTTY,
|
||||
@@ -109,9 +104,9 @@ func TestSpParamsOp(t *testing.T) {
|
||||
// this op configures the container state and does not make calls during toContainer
|
||||
}, &container.Params{
|
||||
Hostname: config.Container.Hostname,
|
||||
RetainSession: config.Container.Tty,
|
||||
HostNet: config.Container.HostNet,
|
||||
HostAbstract: config.Container.HostAbstract,
|
||||
RetainSession: true,
|
||||
HostNet: true,
|
||||
HostAbstract: true,
|
||||
Path: config.Container.Path,
|
||||
Args: config.Container.Args,
|
||||
SeccompFlags: seccomp.AllowMultiarch,
|
||||
@@ -159,7 +154,7 @@ func TestSpFilesystemOp(t *testing.T) {
|
||||
}}},
|
||||
{FilesystemConfig: &hst.FSEphemeral{Target: hst.AbsPrivateTmp}},
|
||||
}
|
||||
c.Container.Device = false
|
||||
c.Container.Flags &= ^hst.FDevice
|
||||
return c
|
||||
}
|
||||
configSmall := newConfigSmall()
|
||||
|
||||
@@ -13,8 +13,7 @@ func init() { gob.Register(new(spDBusOp)) }
|
||||
|
||||
// spDBusOp maintains an xdg-dbus-proxy instance for the container.
|
||||
type spDBusOp struct {
|
||||
// Whether to bind the system bus socket.
|
||||
// Populated during toSystem.
|
||||
// Whether to bind the system bus socket. Populated during toSystem.
|
||||
ProxySystem bool
|
||||
}
|
||||
|
||||
@@ -30,10 +29,10 @@ func (s *spDBusOp) toSystem(state *outcomeStateSys) error {
|
||||
// downstream socket paths
|
||||
sessionPath, systemPath := state.instance().Append("bus"), state.instance().Append("system_bus_socket")
|
||||
|
||||
if err := state.sys.ProxyDBus(
|
||||
state.sessionBus, state.systemBus,
|
||||
sessionPath, systemPath,
|
||||
); err != nil {
|
||||
var sessionBus, systemBus dbus.ProxyPair
|
||||
sessionBus[0], systemBus[0] = state.k.dbusAddress()
|
||||
sessionBus[1], systemBus[1] = sessionPath.String(), systemPath.String()
|
||||
if err := state.sys.ProxyDBus(state.sessionBus, state.systemBus, sessionBus, systemBus); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
227
internal/app/spdbus_test.go
Normal file
227
internal/app/spdbus_test.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/helper"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/acl"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
func TestSpDBusOp(t *testing.T) {
|
||||
config := hst.Template()
|
||||
const instancePrefix = container.Nonexistent + "/tmp/hakurei.0/" + wantAutoEtcPrefix
|
||||
|
||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||
{"not enabled", func(bool, bool) outcomeOp {
|
||||
return new(spDBusOp)
|
||||
}, func() *hst.Config {
|
||||
c := hst.Template()
|
||||
*c.Enablements = 0
|
||||
return c
|
||||
}, nil, nil, nil, nil, errNotEnabled, nil, nil, nil, nil, nil},
|
||||
|
||||
{"invalid", func(bool, bool) outcomeOp {
|
||||
return new(spDBusOp)
|
||||
}, func() *hst.Config {
|
||||
c := hst.Template()
|
||||
c.SessionBus.Talk[0] += "\x00"
|
||||
c.SystemBus = nil
|
||||
return c
|
||||
}, nil, []stub.Call{
|
||||
call("dbusAddress", stub.ExpectArgs{}, [2]string{
|
||||
"unix:path=/run/user/1000/bus",
|
||||
"unix:path=/var/run/dbus/system_bus_socket",
|
||||
}, nil),
|
||||
}, nil, func(t *testing.T, state *outcomeStateSys) {
|
||||
if want := m(instancePrefix); !reflect.DeepEqual(state.sharePath, want) {
|
||||
t.Errorf("outcomeStateSys: sharePath = %v, want %v", state.sharePath, want)
|
||||
}
|
||||
}, &system.OpError{
|
||||
Op: "dbus",
|
||||
Err: syscall.EINVAL,
|
||||
Msg: "message bus proxy configuration contains NUL byte",
|
||||
Revert: false,
|
||||
}, nil, nil, nil, nil, nil},
|
||||
|
||||
{"success default", func(bool, bool) outcomeOp {
|
||||
return new(spDBusOp)
|
||||
}, func() *hst.Config {
|
||||
c := hst.Template()
|
||||
c.SessionBus, c.SystemBus = nil, nil
|
||||
return c
|
||||
}, nil, []stub.Call{
|
||||
call("dbusAddress", stub.ExpectArgs{}, [2]string{
|
||||
"unix:path=/run/user/1000/bus",
|
||||
"unix:path=/var/run/dbus/system_bus_socket",
|
||||
}, nil),
|
||||
call("isVerbose", stub.ExpectArgs{}, true, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"session bus proxy:", []string{
|
||||
"unix:path=/run/user/1000/bus",
|
||||
instancePrefix + "/bus",
|
||||
"--filter",
|
||||
"--talk=org.freedesktop.DBus",
|
||||
"--talk=org.freedesktop.Notifications",
|
||||
"--own=org.chromium.Chromium.*",
|
||||
"--own=org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
||||
"--call=org.freedesktop.portal.*=*",
|
||||
"--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*",
|
||||
}}}, nil, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"message bus proxy final args:", helper.MustNewCheckedArgs(
|
||||
"unix:path=/run/user/1000/bus",
|
||||
instancePrefix+"/bus",
|
||||
"--filter",
|
||||
"--talk=org.freedesktop.DBus",
|
||||
"--talk=org.freedesktop.Notifications",
|
||||
"--own=org.chromium.Chromium.*",
|
||||
"--own=org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
||||
"--call=org.freedesktop.portal.*=*",
|
||||
"--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*",
|
||||
)}}, nil, nil),
|
||||
}, func() *system.I {
|
||||
sys := system.New(panicMsgContext{}, message.NewMsg(nil), checkExpectUid)
|
||||
sys.Ephemeral(system.Process, m(instancePrefix), 0711)
|
||||
if err := sys.ProxyDBus(
|
||||
dbus.NewConfig(config.ID, true, true), nil,
|
||||
dbus.ProxyPair{"unix:path=/run/user/1000/bus", instancePrefix + "/bus"},
|
||||
dbus.ProxyPair{"unix:path=/var/run/dbus/system_bus_socket", instancePrefix + "/system_bus_socket"},
|
||||
); err != nil {
|
||||
t.Fatalf("cannot prepare sys: %v", err)
|
||||
}
|
||||
sys.UpdatePerm(m(instancePrefix+"/bus"), acl.Read, acl.Write)
|
||||
return sys
|
||||
}(), func(t *testing.T, state *outcomeStateSys) {
|
||||
if want := m(instancePrefix); !reflect.DeepEqual(state.sharePath, want) {
|
||||
t.Errorf("outcomeStateSys: sharePath = %v, want %v", state.sharePath, want)
|
||||
}
|
||||
}, nil, func(state *outcomeStateParams) {
|
||||
state.params.Ops = new(container.Ops)
|
||||
|
||||
// emulates spRuntimeOp
|
||||
state.runtimeDir = m("/run/user/1000")
|
||||
}, []stub.Call{
|
||||
// this op configures the container state and does not make calls during toContainer
|
||||
}, &container.Params{
|
||||
Ops: new(container.Ops).
|
||||
Bind(m(instancePrefix+"/bus"),
|
||||
m("/run/user/1000/bus"), 0),
|
||||
}, func(t *testing.T, state *outcomeStateParams) {
|
||||
wantEnv := map[string]string{
|
||||
"DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/1000/bus",
|
||||
}
|
||||
maps.Copy(wantEnv, config.Container.Env)
|
||||
if !maps.Equal(state.env, wantEnv) {
|
||||
t.Errorf("toContainer: env = %#v, want %#v", state.env, wantEnv)
|
||||
}
|
||||
}, nil},
|
||||
|
||||
{"success", func(isShim, _ bool) outcomeOp {
|
||||
if !isShim {
|
||||
return new(spDBusOp)
|
||||
}
|
||||
return &spDBusOp{ProxySystem: true}
|
||||
}, hst.Template, nil, []stub.Call{
|
||||
call("dbusAddress", stub.ExpectArgs{}, [2]string{
|
||||
"unix:path=/run/user/1000/bus",
|
||||
"unix:path=/var/run/dbus/system_bus_socket",
|
||||
}, nil),
|
||||
call("isVerbose", stub.ExpectArgs{}, true, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"session bus proxy:", []string{
|
||||
"unix:path=/run/user/1000/bus",
|
||||
instancePrefix + "/bus",
|
||||
"--filter",
|
||||
"--talk=org.freedesktop.Notifications",
|
||||
"--talk=org.freedesktop.FileManager1",
|
||||
"--talk=org.freedesktop.ScreenSaver",
|
||||
"--talk=org.freedesktop.secrets",
|
||||
"--talk=org.kde.kwalletd5",
|
||||
"--talk=org.kde.kwalletd6",
|
||||
"--talk=org.gnome.SessionManager",
|
||||
"--own=org.chromium.Chromium.*",
|
||||
"--own=org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
||||
"--own=org.mpris.MediaPlayer2.chromium.*",
|
||||
"--call=org.freedesktop.portal.*=*",
|
||||
"--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*",
|
||||
}}}, nil, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"system bus proxy:", []string{
|
||||
"unix:path=/var/run/dbus/system_bus_socket",
|
||||
instancePrefix + "/system_bus_socket",
|
||||
"--filter",
|
||||
"--talk=org.bluez",
|
||||
"--talk=org.freedesktop.Avahi",
|
||||
"--talk=org.freedesktop.UPower",
|
||||
}}}, nil, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"message bus proxy final args:", helper.MustNewCheckedArgs(
|
||||
"unix:path=/run/user/1000/bus",
|
||||
instancePrefix+"/bus",
|
||||
"--filter",
|
||||
"--talk=org.freedesktop.Notifications",
|
||||
"--talk=org.freedesktop.FileManager1",
|
||||
"--talk=org.freedesktop.ScreenSaver",
|
||||
"--talk=org.freedesktop.secrets",
|
||||
"--talk=org.kde.kwalletd5",
|
||||
"--talk=org.kde.kwalletd6",
|
||||
"--talk=org.gnome.SessionManager",
|
||||
"--own=org.chromium.Chromium.*",
|
||||
"--own=org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
||||
"--own=org.mpris.MediaPlayer2.chromium.*",
|
||||
"--call=org.freedesktop.portal.*=*",
|
||||
"--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*",
|
||||
|
||||
"unix:path=/var/run/dbus/system_bus_socket",
|
||||
instancePrefix+"/system_bus_socket",
|
||||
"--filter",
|
||||
"--talk=org.bluez",
|
||||
"--talk=org.freedesktop.Avahi",
|
||||
"--talk=org.freedesktop.UPower",
|
||||
)}}, nil, nil),
|
||||
}, func() *system.I {
|
||||
sys := system.New(panicMsgContext{}, message.NewMsg(nil), checkExpectUid)
|
||||
sys.Ephemeral(system.Process, m(instancePrefix), 0711)
|
||||
if err := sys.ProxyDBus(
|
||||
config.SessionBus, config.SystemBus,
|
||||
dbus.ProxyPair{"unix:path=/run/user/1000/bus", instancePrefix + "/bus"},
|
||||
dbus.ProxyPair{"unix:path=/var/run/dbus/system_bus_socket", instancePrefix + "/system_bus_socket"},
|
||||
); err != nil {
|
||||
t.Fatalf("cannot prepare sys: %v", err)
|
||||
}
|
||||
sys.UpdatePerm(m(instancePrefix+"/bus"), acl.Read, acl.Write).
|
||||
UpdatePerm(m(instancePrefix+"/system_bus_socket"), acl.Read, acl.Write)
|
||||
return sys
|
||||
}(), func(t *testing.T, state *outcomeStateSys) {
|
||||
if want := m(instancePrefix); !reflect.DeepEqual(state.sharePath, want) {
|
||||
t.Errorf("outcomeStateSys: sharePath = %v, want %v", state.sharePath, want)
|
||||
}
|
||||
}, nil, func(state *outcomeStateParams) {
|
||||
state.params.Ops = new(container.Ops)
|
||||
|
||||
// emulates spRuntimeOp
|
||||
state.runtimeDir = m("/run/user/1000")
|
||||
}, []stub.Call{
|
||||
// this op configures the container state and does not make calls during toContainer
|
||||
}, &container.Params{
|
||||
Ops: new(container.Ops).
|
||||
Bind(m(instancePrefix+"/bus"),
|
||||
m("/run/user/1000/bus"), 0).
|
||||
Bind(m(instancePrefix+"/system_bus_socket"),
|
||||
m("/var/run/dbus/system_bus_socket"), 0),
|
||||
}, func(t *testing.T, state *outcomeStateParams) {
|
||||
wantEnv := map[string]string{
|
||||
"DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/1000/bus",
|
||||
"DBUS_SYSTEM_BUS_ADDRESS": "unix:path=/var/run/dbus/system_bus_socket",
|
||||
}
|
||||
maps.Copy(wantEnv, config.Container.Env)
|
||||
if !maps.Equal(state.env, wantEnv) {
|
||||
t.Errorf("toContainer: env = %#v, want %#v", state.env, wantEnv)
|
||||
}
|
||||
}, nil},
|
||||
})
|
||||
}
|
||||
@@ -21,8 +21,9 @@ type spFinalOp struct{}
|
||||
|
||||
func (s spFinalOp) toSystem(state *outcomeStateSys) error {
|
||||
// append ExtraPerms last
|
||||
for _, p := range state.extraPerms {
|
||||
if p == nil || p.Path == nil {
|
||||
for i := range state.extraPerms {
|
||||
p := &state.extraPerms[i]
|
||||
if p.Path == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ func (s *spX11Op) toSystem(state *outcomeStateSys) error {
|
||||
}
|
||||
} else {
|
||||
state.sys.UpdatePermType(hst.EX11, socketPath, acl.Read, acl.Write, acl.Execute)
|
||||
if !state.Container.HostAbstract {
|
||||
if state.Container.Flags&hst.FHostAbstract == 0 {
|
||||
s.Display = "unix:" + socketPath.String()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,18 +12,19 @@ import (
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrDBusConfig = errors.New("dbus config not supplied")
|
||||
)
|
||||
// ErrDBusConfig is returned when a required [hst.BusConfig] argument is nil.
|
||||
var ErrDBusConfig = errors.New("dbus config not supplied")
|
||||
|
||||
// MustProxyDBus calls ProxyDBus and panics if an error is returned.
|
||||
func (sys *I) MustProxyDBus(sessionPath *check.Absolute, session *hst.BusConfig, systemPath *check.Absolute, system *hst.BusConfig) *I {
|
||||
if err := sys.ProxyDBus(session, system, sessionPath, systemPath); err != nil {
|
||||
func (sys *I) MustProxyDBus(
|
||||
session, system *hst.BusConfig,
|
||||
sessionBus, systemBus dbus.ProxyPair,
|
||||
) *I {
|
||||
if err := sys.ProxyDBus(session, system, sessionBus, systemBus); err != nil {
|
||||
panic(err.Error())
|
||||
} else {
|
||||
return sys
|
||||
@@ -32,7 +33,10 @@ func (sys *I) MustProxyDBus(sessionPath *check.Absolute, session *hst.BusConfig,
|
||||
|
||||
// ProxyDBus finalises configuration ahead of time and starts xdg-dbus-proxy via [dbus] and terminates it on revert.
|
||||
// This [Op] is always [Process] scoped.
|
||||
func (sys *I) ProxyDBus(session, system *hst.BusConfig, sessionPath, systemPath *check.Absolute) error {
|
||||
func (sys *I) ProxyDBus(
|
||||
session, system *hst.BusConfig,
|
||||
sessionBus, systemBus dbus.ProxyPair,
|
||||
) error {
|
||||
d := new(dbusProxyOp)
|
||||
|
||||
// session bus is required as otherwise this is effectively a very expensive noop
|
||||
@@ -44,9 +48,6 @@ func (sys *I) ProxyDBus(session, system *hst.BusConfig, sessionPath, systemPath
|
||||
// system bus is optional
|
||||
d.system = system != nil
|
||||
|
||||
var sessionBus, systemBus dbus.ProxyPair
|
||||
sessionBus[0], systemBus[0] = sys.dbusAddress()
|
||||
sessionBus[1], systemBus[1] = sessionPath.String(), systemPath.String()
|
||||
d.out = &linePrefixWriter{println: log.Println, prefix: "(dbus) ", buf: new(strings.Builder)}
|
||||
if final, err := sys.dbusFinalise(sessionBus, systemBus, session, system); err != nil {
|
||||
if errors.Is(err, syscall.EINVAL) {
|
||||
|
||||
@@ -8,7 +8,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
/*SessionBusAddress is the name of the environment variable where the address of the login session message bus is given in.
|
||||
/*
|
||||
SessionBusAddress is the name of the environment variable where the address of the login session message bus is given in.
|
||||
|
||||
If that variable is not set, applications may also try to read the address from the X Window System root window property _DBUS_SESSION_BUS_ADDRESS.
|
||||
The root window property must have type STRING. The environment variable should have precedence over the root window property.
|
||||
@@ -22,13 +23,16 @@ const (
|
||||
Therefore, it is recommended that applications do not try to make this determination for their functionality purposes, and instead they should attempt to start the server.
|
||||
|
||||
This package diverges from the specification, as the caller is unlikely to be an X client, or be in a position to autolaunch a dbus server.
|
||||
So a fallback address with a socket located in the well-known default XDG_RUNTIME_DIR formatting is used.*/
|
||||
So a fallback address with a socket located in the well-known default XDG_RUNTIME_DIR formatting is used.
|
||||
*/
|
||||
SessionBusAddress = "DBUS_SESSION_BUS_ADDRESS"
|
||||
|
||||
/*SystemBusAddress is the name of the environment variable where the address of the system message bus is given in.
|
||||
/*
|
||||
SystemBusAddress is the name of the environment variable where the address of the system message bus is given in.
|
||||
|
||||
If that variable is not set, applications should try to connect to the well-known address unix:path=/var/run/dbus/system_bus_socket.
|
||||
Implementations of the well-known system bus should listen on an address that will result in that connection being successful.*/
|
||||
Implementations of the well-known system bus should listen on an address that will result in that connection being successful.
|
||||
*/
|
||||
SystemBusAddress = "DBUS_SYSTEM_BUS_ADDRESS"
|
||||
|
||||
// FallbackSystemBusAddress is used when [SystemBusAddress] is not set.
|
||||
@@ -36,28 +40,30 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
addresses [2]string
|
||||
address [2]string
|
||||
addressOnce sync.Once
|
||||
)
|
||||
|
||||
// Address returns the session and system bus addresses copied from environment,
|
||||
// or appropriate fallback values if they are not set.
|
||||
func Address() (session, system string) {
|
||||
addressOnce.Do(func() {
|
||||
// resolve upstream session bus address
|
||||
if addr, ok := os.LookupEnv(SessionBusAddress); !ok {
|
||||
// fall back to default format
|
||||
addresses[0] = fmt.Sprintf("unix:path=/run/user/%d/bus", os.Getuid())
|
||||
address[0] = fmt.Sprintf("unix:path=/run/user/%d/bus", os.Getuid())
|
||||
} else {
|
||||
addresses[0] = addr
|
||||
address[0] = addr
|
||||
}
|
||||
|
||||
// resolve upstream system bus address
|
||||
if addr, ok := os.LookupEnv(SystemBusAddress); !ok {
|
||||
// fall back to default hardcoded value
|
||||
addresses[1] = FallbackSystemBusAddress
|
||||
address[1] = FallbackSystemBusAddress
|
||||
} else {
|
||||
addresses[1] = addr
|
||||
address[1] = addr
|
||||
}
|
||||
})
|
||||
|
||||
return addresses[0], addresses[1]
|
||||
return address[0], address[1]
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ func Finalise(sessionBus, systemBus ProxyPair, session, system *hst.BusConfig) (
|
||||
|
||||
final = &Final{Session: sessionBus, System: systemBus}
|
||||
|
||||
final.WriterTo, err = helper.NewCheckedArgs(args)
|
||||
final.WriterTo, err = helper.NewCheckedArgs(args...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ func TestDBusProxyOp(t *testing.T) {
|
||||
Op: "dbus", Err: ErrDBusConfig,
|
||||
Msg: "attempted to create message bus proxy args without session bus config",
|
||||
}
|
||||
if err := sys.ProxyDBus(nil, new(hst.BusConfig), nil, nil); !reflect.DeepEqual(err, wantErr) {
|
||||
if err := sys.ProxyDBus(nil, new(hst.BusConfig), dbus.ProxyPair{}, dbus.ProxyPair{}); !reflect.DeepEqual(err, wantErr) {
|
||||
t.Errorf("ProxyDBus: error = %v, want %v", err, wantErr)
|
||||
}
|
||||
}, nil, stub.Expect{}},
|
||||
@@ -99,15 +99,20 @@ func TestDBusProxyOp(t *testing.T) {
|
||||
}()
|
||||
|
||||
sys.MustProxyDBus(
|
||||
m("/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus"), &hst.BusConfig{
|
||||
&hst.BusConfig{
|
||||
// use impossible value here as an implicit assert that it goes through the stub
|
||||
Talk: []string{"session\x00"}, Filter: true,
|
||||
}, m("/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket"), &hst.BusConfig{
|
||||
}, &hst.BusConfig{
|
||||
// use impossible value here as an implicit assert that it goes through the stub
|
||||
Talk: []string{"system\x00"}, Filter: true,
|
||||
}, dbus.ProxyPair{
|
||||
"unix:path=/run/user/1000/bus",
|
||||
"/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus",
|
||||
}, dbus.ProxyPair{
|
||||
"unix:path=/run/dbus/system_bus_socket",
|
||||
"/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket",
|
||||
})
|
||||
}, nil, stub.Expect{Calls: []stub.Call{
|
||||
call("dbusAddress", stub.ExpectArgs{}, [2]string{"unix:path=/run/user/1000/bus", "unix:path=/run/dbus/system_bus_socket"}, nil),
|
||||
call("dbusFinalise", stub.ExpectArgs{
|
||||
dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus"},
|
||||
dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket"},
|
||||
@@ -128,13 +133,16 @@ func TestDBusProxyOp(t *testing.T) {
|
||||
}, &hst.BusConfig{
|
||||
// use impossible value here as an implicit assert that it goes through the stub
|
||||
Talk: []string{"system\x00"}, Filter: true,
|
||||
},
|
||||
m("/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus"),
|
||||
m("/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket")); !reflect.DeepEqual(err, wantErr) {
|
||||
}, dbus.ProxyPair{
|
||||
"unix:path=/run/user/1000/bus",
|
||||
"/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus",
|
||||
}, dbus.ProxyPair{
|
||||
"unix:path=/run/dbus/system_bus_socket",
|
||||
"/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket",
|
||||
}); !reflect.DeepEqual(err, wantErr) {
|
||||
t.Errorf("ProxyDBus: error = %v", err)
|
||||
}
|
||||
}, nil, stub.Expect{Calls: []stub.Call{
|
||||
call("dbusAddress", stub.ExpectArgs{}, [2]string{"unix:path=/run/user/1000/bus", "unix:path=/run/dbus/system_bus_socket"}, nil),
|
||||
call("dbusFinalise", stub.ExpectArgs{
|
||||
dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus"},
|
||||
dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket"},
|
||||
@@ -145,12 +153,18 @@ func TestDBusProxyOp(t *testing.T) {
|
||||
|
||||
{"full", 0xcafebabe, func(_ *testing.T, sys *I) {
|
||||
sys.MustProxyDBus(
|
||||
m("/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus"), &hst.BusConfig{
|
||||
&hst.BusConfig{
|
||||
// use impossible value here as an implicit assert that it goes through the stub
|
||||
Talk: []string{"session\x00"}, Filter: true,
|
||||
}, m("/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket"), &hst.BusConfig{
|
||||
}, &hst.BusConfig{
|
||||
// use impossible value here as an implicit assert that it goes through the stub
|
||||
Talk: []string{"system\x00"}, Filter: true,
|
||||
}, dbus.ProxyPair{
|
||||
"unix:path=/run/user/1000/bus",
|
||||
"/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus",
|
||||
}, dbus.ProxyPair{
|
||||
"unix:path=/run/dbus/system_bus_socket",
|
||||
"/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket",
|
||||
})
|
||||
}, []Op{
|
||||
&dbusProxyOp{
|
||||
@@ -158,7 +172,6 @@ func TestDBusProxyOp(t *testing.T) {
|
||||
system: true,
|
||||
},
|
||||
}, stub.Expect{Calls: []stub.Call{
|
||||
call("dbusAddress", stub.ExpectArgs{}, [2]string{"unix:path=/run/user/1000/bus", "unix:path=/run/dbus/system_bus_socket"}, nil),
|
||||
call("dbusFinalise", stub.ExpectArgs{
|
||||
dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus"},
|
||||
dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket"},
|
||||
@@ -168,7 +181,7 @@ func TestDBusProxyOp(t *testing.T) {
|
||||
call("isVerbose", stub.ExpectArgs{}, true, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"session bus proxy:", []string{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus", "--filter", "--talk=session\x00"}}}, nil, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"system bus proxy:", []string{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket", "--filter", "--talk=system\x00"}}}, nil, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"message bus proxy final args:", helper.MustNewCheckedArgs([]string{"unique", "value", "0", "injected", "by", "the", "test", "suite"})}}, nil, nil),
|
||||
call("verbose", stub.ExpectArgs{[]any{"message bus proxy final args:", helper.MustNewCheckedArgs("unique", "value", "0", "injected", "by", "the", "test", "suite")}}, nil, nil),
|
||||
}}},
|
||||
})
|
||||
|
||||
@@ -183,10 +196,10 @@ func TestDBusProxyOp(t *testing.T) {
|
||||
SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
|
||||
SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
|
||||
|
||||
WriterTo: helper.MustNewCheckedArgs([]string{
|
||||
WriterTo: helper.MustNewCheckedArgs(
|
||||
"--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
|
||||
"--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
|
||||
}),
|
||||
),
|
||||
}, system: false,
|
||||
}, &dbusProxyOp{final: &dbus.Final{
|
||||
Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
|
||||
@@ -195,10 +208,10 @@ func TestDBusProxyOp(t *testing.T) {
|
||||
SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
|
||||
SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
|
||||
|
||||
WriterTo: helper.MustNewCheckedArgs([]string{
|
||||
WriterTo: helper.MustNewCheckedArgs(
|
||||
"--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
|
||||
"--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
|
||||
}),
|
||||
),
|
||||
}, system: true,
|
||||
}, false},
|
||||
|
||||
@@ -209,10 +222,10 @@ func TestDBusProxyOp(t *testing.T) {
|
||||
SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
|
||||
SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
|
||||
|
||||
WriterTo: helper.MustNewCheckedArgs([]string{
|
||||
WriterTo: helper.MustNewCheckedArgs(
|
||||
"--filter", "unix:path=/run/user/1001/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
|
||||
"--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
|
||||
}),
|
||||
),
|
||||
}, system: true,
|
||||
}, &dbusProxyOp{final: &dbus.Final{
|
||||
Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
|
||||
@@ -221,10 +234,10 @@ func TestDBusProxyOp(t *testing.T) {
|
||||
SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
|
||||
SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
|
||||
|
||||
WriterTo: helper.MustNewCheckedArgs([]string{
|
||||
WriterTo: helper.MustNewCheckedArgs(
|
||||
"--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
|
||||
"--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
|
||||
}),
|
||||
),
|
||||
}, system: true,
|
||||
}, false},
|
||||
|
||||
@@ -235,10 +248,10 @@ func TestDBusProxyOp(t *testing.T) {
|
||||
SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
|
||||
SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket\x00"}}}},
|
||||
|
||||
WriterTo: helper.MustNewCheckedArgs([]string{
|
||||
WriterTo: helper.MustNewCheckedArgs(
|
||||
"--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
|
||||
"--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
|
||||
}),
|
||||
),
|
||||
}, system: true,
|
||||
}, &dbusProxyOp{final: &dbus.Final{
|
||||
Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
|
||||
@@ -247,10 +260,10 @@ func TestDBusProxyOp(t *testing.T) {
|
||||
SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
|
||||
SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
|
||||
|
||||
WriterTo: helper.MustNewCheckedArgs([]string{
|
||||
WriterTo: helper.MustNewCheckedArgs(
|
||||
"--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
|
||||
"--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
|
||||
}),
|
||||
),
|
||||
}, system: true,
|
||||
}, false},
|
||||
|
||||
@@ -261,10 +274,10 @@ func TestDBusProxyOp(t *testing.T) {
|
||||
SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1001/bus"}}}},
|
||||
SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
|
||||
|
||||
WriterTo: helper.MustNewCheckedArgs([]string{
|
||||
WriterTo: helper.MustNewCheckedArgs(
|
||||
"--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
|
||||
"--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
|
||||
}),
|
||||
),
|
||||
}, system: true,
|
||||
}, &dbusProxyOp{final: &dbus.Final{
|
||||
Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
|
||||
@@ -273,10 +286,10 @@ func TestDBusProxyOp(t *testing.T) {
|
||||
SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
|
||||
SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
|
||||
|
||||
WriterTo: helper.MustNewCheckedArgs([]string{
|
||||
WriterTo: helper.MustNewCheckedArgs(
|
||||
"--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
|
||||
"--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
|
||||
}),
|
||||
),
|
||||
}, system: true,
|
||||
}, false},
|
||||
|
||||
@@ -287,10 +300,10 @@ func TestDBusProxyOp(t *testing.T) {
|
||||
SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
|
||||
SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
|
||||
|
||||
WriterTo: helper.MustNewCheckedArgs([]string{
|
||||
WriterTo: helper.MustNewCheckedArgs(
|
||||
"--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
|
||||
"--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
|
||||
}),
|
||||
),
|
||||
}, system: true,
|
||||
}, &dbusProxyOp{final: &dbus.Final{
|
||||
Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
|
||||
@@ -299,10 +312,10 @@ func TestDBusProxyOp(t *testing.T) {
|
||||
SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
|
||||
SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
|
||||
|
||||
WriterTo: helper.MustNewCheckedArgs([]string{
|
||||
WriterTo: helper.MustNewCheckedArgs(
|
||||
"--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
|
||||
"--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
|
||||
}),
|
||||
),
|
||||
}, system: true,
|
||||
}, false},
|
||||
|
||||
@@ -313,10 +326,10 @@ func TestDBusProxyOp(t *testing.T) {
|
||||
SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
|
||||
SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
|
||||
|
||||
WriterTo: helper.MustNewCheckedArgs([]string{
|
||||
WriterTo: helper.MustNewCheckedArgs(
|
||||
"--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
|
||||
"--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
|
||||
}),
|
||||
),
|
||||
}, system: true,
|
||||
}, &dbusProxyOp{final: &dbus.Final{
|
||||
Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
|
||||
@@ -325,10 +338,10 @@ func TestDBusProxyOp(t *testing.T) {
|
||||
SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
|
||||
SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
|
||||
|
||||
WriterTo: helper.MustNewCheckedArgs([]string{
|
||||
WriterTo: helper.MustNewCheckedArgs(
|
||||
"--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
|
||||
"--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
|
||||
}),
|
||||
),
|
||||
}, system: true,
|
||||
}, false},
|
||||
|
||||
@@ -339,10 +352,10 @@ func TestDBusProxyOp(t *testing.T) {
|
||||
SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
|
||||
SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
|
||||
|
||||
WriterTo: helper.MustNewCheckedArgs([]string{
|
||||
WriterTo: helper.MustNewCheckedArgs(
|
||||
"--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
|
||||
"--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
|
||||
}),
|
||||
),
|
||||
}, system: true,
|
||||
}, &dbusProxyOp{final: &dbus.Final{
|
||||
Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
|
||||
@@ -351,10 +364,10 @@ func TestDBusProxyOp(t *testing.T) {
|
||||
SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
|
||||
SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
|
||||
|
||||
WriterTo: helper.MustNewCheckedArgs([]string{
|
||||
WriterTo: helper.MustNewCheckedArgs(
|
||||
"--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
|
||||
"--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
|
||||
}),
|
||||
),
|
||||
}, system: true,
|
||||
}, true},
|
||||
})
|
||||
@@ -374,7 +387,7 @@ func dbusNewFinalSample(v int) *dbus.Final {
|
||||
SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
|
||||
SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/dbus/system_bus_socket"}}}},
|
||||
|
||||
WriterTo: helper.MustNewCheckedArgs([]string{"unique", "value", strconv.Itoa(v), "injected", "by", "the", "test", "suite"}),
|
||||
WriterTo: helper.MustNewCheckedArgs("unique", "value", strconv.Itoa(v), "injected", "by", "the", "test", "suite"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,8 +48,6 @@ type syscallDispatcher interface {
|
||||
// xcbChangeHosts provides [xcb.ChangeHosts].
|
||||
xcbChangeHosts(mode xcb.HostMode, family xcb.Family, address string) error
|
||||
|
||||
// dbusAddress provides [dbus.Address].
|
||||
dbusAddress() (session, system string)
|
||||
// dbusFinalise provides [dbus.Finalise].
|
||||
dbusFinalise(sessionBus, systemBus dbus.ProxyPair, session, system *hst.BusConfig) (final *dbus.Final, err error)
|
||||
// dbusProxyStart provides the Start method of [dbus.Proxy].
|
||||
@@ -82,10 +80,6 @@ func (k direct) xcbChangeHosts(mode xcb.HostMode, family xcb.Family, address str
|
||||
return xcb.ChangeHosts(mode, family, address)
|
||||
}
|
||||
|
||||
func (k direct) dbusAddress() (session, system string) {
|
||||
return dbus.Address()
|
||||
}
|
||||
|
||||
func (k direct) dbusFinalise(sessionBus, systemBus dbus.ProxyPair, session, system *hst.BusConfig) (final *dbus.Final, err error) {
|
||||
return dbus.Finalise(sessionBus, systemBus, session, system)
|
||||
}
|
||||
|
||||
@@ -276,12 +276,6 @@ func (k *kstub) xcbChangeHosts(mode xcb.HostMode, family xcb.Family, address str
|
||||
stub.CheckArg(k.Stub, "address", address, 2))
|
||||
}
|
||||
|
||||
func (k *kstub) dbusAddress() (session, system string) {
|
||||
k.Helper()
|
||||
ret := k.Expects("dbusAddress").Ret.([2]string)
|
||||
return ret[0], ret[1]
|
||||
}
|
||||
|
||||
func (k *kstub) dbusFinalise(sessionBus, systemBus dbus.ProxyPair, session, system *hst.BusConfig) (final *dbus.Final, err error) {
|
||||
k.Helper()
|
||||
expect := k.Expects("dbusFinalise")
|
||||
|
||||
@@ -214,8 +214,8 @@ in
|
||||
(ent "/" "/.hakurei" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000004,gid=1000004")
|
||||
(ent "/" "/dev" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||
(ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666")
|
||||
(ent "/" "/dev/shm" "rw,nosuid,nodev" "tmpfs" "tmpfs" "rw")
|
||||
(ent "/" ignore ignore ignore ignore ignore) # order not deterministic
|
||||
(ent "/" ignore ignore ignore ignore ignore) # not deterministic
|
||||
(ent "/" ignore ignore ignore ignore ignore)
|
||||
(ent "/" ignore ignore ignore ignore ignore)
|
||||
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=1000004,gid=1000004")
|
||||
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
|
||||
Reference in New Issue
Block a user