Compare commits
No commits in common. "master" and "v0.3.1" have entirely different histories.
@ -1,2 +0,0 @@
|
||||
ColumnLimit: 0
|
||||
IndentWidth: 4
|
||||
@ -11,24 +11,21 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
_ "unsafe" // for go:linkname
|
||||
_ "unsafe"
|
||||
|
||||
"hakurei.app/command"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/dbus"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/env"
|
||||
"hakurei.app/internal/info"
|
||||
"hakurei.app/internal/outcome"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
// optionalErrorUnwrap calls [errors.Unwrap] and returns the resulting value
|
||||
// if it is not nil, or the original value if it is.
|
||||
//
|
||||
//go:linkname optionalErrorUnwrap hakurei.app/container.optionalErrorUnwrap
|
||||
func optionalErrorUnwrap(err error) error
|
||||
func optionalErrorUnwrap(_ error) error
|
||||
|
||||
func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErrs, out io.Writer) command.Command {
|
||||
var (
|
||||
@ -353,7 +350,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
||||
}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id")
|
||||
}
|
||||
|
||||
c.Command("version", "Display version information", func(args []string) error { fmt.Println(info.Version()); return errSuccess })
|
||||
c.Command("version", "Display version information", func(args []string) error { fmt.Println(internal.Version()); return errSuccess })
|
||||
c.Command("license", "Show full license text", func(args []string) error { fmt.Println(license); return errSuccess })
|
||||
c.Command("template", "Produce a config template", func(args []string) error { encodeJSON(log.Fatal, os.Stdout, false, hst.Template()); return errSuccess })
|
||||
c.Command("help", "Show this help message", func([]string) error { c.PrintHelp(); return errSuccess })
|
||||
|
||||
@ -1,13 +1,18 @@
|
||||
package main
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
_ "unsafe"
|
||||
|
||||
"hakurei.app/container/stub"
|
||||
)
|
||||
|
||||
//go:linkname decodeJSON hakurei.app/cmd/hakurei.decodeJSON
|
||||
func decodeJSON(fatal func(v ...any), op string, r io.Reader, v any)
|
||||
|
||||
func TestDecodeJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@ -57,6 +62,9 @@ func TestDecodeJSON(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
//go:linkname encodeJSON hakurei.app/cmd/hakurei.encodeJSON
|
||||
func encodeJSON(fatal func(v ...any), output io.Writer, short bool, v any)
|
||||
|
||||
func TestEncodeJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@ -66,7 +74,7 @@ func TestEncodeJSON(t *testing.T) {
|
||||
want string
|
||||
}{
|
||||
{"marshaler", errorJSONMarshaler{},
|
||||
`cannot encode json for main.errorJSONMarshaler: unique error 3735928559 injected by the test suite`},
|
||||
`cannot encode json for main_test.errorJSONMarshaler: unique error 3735928559 injected by the test suite`},
|
||||
{"default", func() {},
|
||||
`cannot write json: json: unsupported type: func()`},
|
||||
}
|
||||
|
||||
@ -12,8 +12,8 @@ import (
|
||||
"time"
|
||||
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/env"
|
||||
"hakurei.app/internal/info"
|
||||
"hakurei.app/internal/outcome"
|
||||
"hakurei.app/internal/store"
|
||||
"hakurei.app/message"
|
||||
@ -24,20 +24,20 @@ func printShowSystem(output io.Writer, short, flagJSON bool) {
|
||||
t := newPrinter(output)
|
||||
defer t.MustFlush()
|
||||
|
||||
hi := &hst.Info{Version: info.Version(), User: new(outcome.Hsu).MustID(nil)}
|
||||
env.CopyPaths().Copy(&hi.Paths, hi.User)
|
||||
info := &hst.Info{Version: internal.Version(), User: new(outcome.Hsu).MustID(nil)}
|
||||
env.CopyPaths().Copy(&info.Paths, info.User)
|
||||
|
||||
if flagJSON {
|
||||
encodeJSON(log.Fatal, output, short, hi)
|
||||
encodeJSON(log.Fatal, output, short, info)
|
||||
return
|
||||
}
|
||||
|
||||
t.Printf("Version:\t%s\n", hi.Version)
|
||||
t.Printf("User:\t%d\n", hi.User)
|
||||
t.Printf("TempDir:\t%s\n", hi.TempDir)
|
||||
t.Printf("SharePath:\t%s\n", hi.SharePath)
|
||||
t.Printf("RuntimePath:\t%s\n", hi.RuntimePath)
|
||||
t.Printf("RunDirPath:\t%s\n", hi.RunDirPath)
|
||||
t.Printf("Version:\t%s\n", info.Version)
|
||||
t.Printf("User:\t%d\n", info.User)
|
||||
t.Printf("TempDir:\t%s\n", info.TempDir)
|
||||
t.Printf("SharePath:\t%s\n", info.SharePath)
|
||||
t.Printf("RuntimePath:\t%s\n", info.RuntimePath)
|
||||
t.Printf("RunDirPath:\t%s\n", info.RunDirPath)
|
||||
}
|
||||
|
||||
// printShowInstance writes a representation of [hst.State] or [hst.Config] to output.
|
||||
@ -90,6 +90,12 @@ func printShowInstance(
|
||||
t.Printf(" Groups:\t%s\n", strings.Join(config.Groups, ", "))
|
||||
}
|
||||
if config.Container != nil {
|
||||
if config.Container.Home != nil {
|
||||
t.Printf(" Home:\t%s\n", config.Container.Home)
|
||||
}
|
||||
if config.Container.Hostname != "" {
|
||||
t.Printf(" Hostname:\t%s\n", config.Container.Hostname)
|
||||
}
|
||||
flags := config.Container.Flags.String()
|
||||
|
||||
// this is included in the upper hst.Config struct but is relevant here
|
||||
@ -104,12 +110,6 @@ func printShowInstance(
|
||||
}
|
||||
t.Printf(" Flags:\t%s\n", flags)
|
||||
|
||||
if config.Container.Home != nil {
|
||||
t.Printf(" Home:\t%s\n", config.Container.Home)
|
||||
}
|
||||
if config.Container.Hostname != "" {
|
||||
t.Printf(" Hostname:\t%s\n", config.Container.Hostname)
|
||||
}
|
||||
if config.Container.Path != nil {
|
||||
t.Printf(" Path:\t%s\n", config.Container.Path)
|
||||
}
|
||||
|
||||
@ -64,9 +64,9 @@ func TestPrintShowInstance(t *testing.T) {
|
||||
Identity: 9 (org.chromium.Chromium)
|
||||
Enablements: wayland, dbus, pulseaudio
|
||||
Groups: video, dialout, plugdev
|
||||
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
|
||||
Home: /data/data/org.chromium.Chromium
|
||||
Hostname: localhost
|
||||
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
|
||||
Path: /run/current-system/sw/bin/chromium
|
||||
Arguments: chromium --ignore-gpu-blocklist --disable-smooth-scrolling --enable-features=UseOzonePlatform --ozone-platform=wayland
|
||||
|
||||
@ -161,9 +161,9 @@ App
|
||||
Identity: 9 (org.chromium.Chromium)
|
||||
Enablements: wayland, dbus, pulseaudio
|
||||
Groups: video, dialout, plugdev
|
||||
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
|
||||
Home: /data/data/org.chromium.Chromium
|
||||
Hostname: localhost
|
||||
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
|
||||
Path: /run/current-system/sw/bin/chromium
|
||||
Arguments: chromium --ignore-gpu-blocklist --disable-smooth-scrolling --enable-features=UseOzonePlatform --ozone-platform=wayland
|
||||
|
||||
|
||||
@ -10,11 +10,11 @@ import (
|
||||
"os/exec"
|
||||
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/info"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
var hakureiPathVal = info.MustHakureiPath().String()
|
||||
var hakureiPathVal = internal.MustHakureiPath().String()
|
||||
|
||||
func mustRunApp(ctx context.Context, msg message.Msg, config *hst.Config, beforeFail func()) {
|
||||
var (
|
||||
|
||||
@ -56,7 +56,7 @@ func NewAbs(pathname string) (*Absolute, error) {
|
||||
// MustAbs calls [NewAbs] and panics on error.
|
||||
func MustAbs(pathname string) *Absolute {
|
||||
if a, err := NewAbs(pathname); err != nil {
|
||||
panic(err)
|
||||
panic(err.Error())
|
||||
} else {
|
||||
return a
|
||||
}
|
||||
|
||||
@ -14,10 +14,8 @@ import (
|
||||
. "hakurei.app/container/check"
|
||||
)
|
||||
|
||||
// unsafeAbs returns check.Absolute on any string value.
|
||||
//
|
||||
//go:linkname unsafeAbs hakurei.app/container/check.unsafeAbs
|
||||
func unsafeAbs(pathname string) *Absolute
|
||||
func unsafeAbs(_ string) *Absolute
|
||||
|
||||
func TestAbsoluteError(t *testing.T) {
|
||||
t.Parallel()
|
||||
@ -84,9 +82,9 @@ func TestNewAbs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
wantPanic := &AbsoluteError{Pathname: "etc"}
|
||||
wantPanic := `path "etc" is not absolute`
|
||||
|
||||
if r := recover(); !reflect.DeepEqual(r, wantPanic) {
|
||||
if r := recover(); r != wantPanic {
|
||||
t.Errorf("MustAbs: panic = %v; want %v", r, wantPanic)
|
||||
}
|
||||
}()
|
||||
|
||||
@ -21,7 +21,6 @@ import (
|
||||
"hakurei.app/command"
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/container/std"
|
||||
"hakurei.app/container/vfs"
|
||||
@ -30,45 +29,6 @@ import (
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
// Note: this package requires cgo, which is unavailable in the Go playground.
|
||||
func Example() {
|
||||
// Must be called early if the current process starts containers.
|
||||
container.TryArgv0(nil)
|
||||
|
||||
// Configure the container.
|
||||
z := container.New(context.Background(), nil)
|
||||
z.Hostname = "hakurei-example"
|
||||
z.Proc(fhs.AbsProc).Dev(fhs.AbsDev, true)
|
||||
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||
|
||||
// Bind / for demonstration.
|
||||
z.Bind(fhs.AbsRoot, fhs.AbsRoot, 0)
|
||||
if name, err := exec.LookPath("hostname"); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
z.Path = check.MustAbs(name)
|
||||
}
|
||||
|
||||
// This completes the first stage of container setup and starts the container init process.
|
||||
// The new process blocks until the Serve method is called.
|
||||
if err := z.Start(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// This serves the setup payload to the container init process,
|
||||
// starting the second stage of container setup.
|
||||
if err := z.Serve(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Must be called if the Start method succeeds.
|
||||
if err := z.Wait(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Output: hakurei-example
|
||||
}
|
||||
|
||||
func TestStartError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@ -762,14 +722,12 @@ func TestMain(m *testing.M) {
|
||||
func helperNewContainerLibPaths(ctx context.Context, libPaths *[]*check.Absolute, args ...string) (c *container.Container) {
|
||||
msg := message.New(nil)
|
||||
msg.SwapVerbose(testing.Verbose())
|
||||
executable := check.MustAbs(container.MustExecutable(msg))
|
||||
|
||||
c = container.NewCommand(ctx, msg, absHelperInnerPath, "helper", args...)
|
||||
c.Env = append(c.Env, envDoCheck+"=1")
|
||||
c.Bind(executable, absHelperInnerPath, 0)
|
||||
c.Bind(check.MustAbs(os.Args[0]), absHelperInnerPath, 0)
|
||||
|
||||
// in case test has cgo enabled
|
||||
if entries, err := ldd.Resolve(ctx, msg, executable); err != nil {
|
||||
if entries, err := ldd.Exec(ctx, msg, os.Args[0]); err != nil {
|
||||
log.Fatalf("ldd: %v", err)
|
||||
} else {
|
||||
*libPaths = ldd.Path(entries)
|
||||
|
||||
@ -8,10 +8,8 @@ import (
|
||||
|
||||
/* constants in this file bypass abs check, be extremely careful when changing them! */
|
||||
|
||||
// unsafeAbs returns check.Absolute on any string value.
|
||||
//
|
||||
//go:linkname unsafeAbs hakurei.app/container/check.unsafeAbs
|
||||
func unsafeAbs(pathname string) *check.Absolute
|
||||
func unsafeAbs(_ string) *check.Absolute
|
||||
|
||||
var (
|
||||
// AbsRoot is [Root] as [check.Absolute].
|
||||
@ -36,8 +34,6 @@ var (
|
||||
|
||||
// AbsDev is [Dev] as [check.Absolute].
|
||||
AbsDev = unsafeAbs(Dev)
|
||||
// AbsDevShm is [DevShm] as [check.Absolute].
|
||||
AbsDevShm = unsafeAbs(DevShm)
|
||||
// AbsProc is [Proc] as [check.Absolute].
|
||||
AbsProc = unsafeAbs(Proc)
|
||||
// AbsSys is [Sys] as [check.Absolute].
|
||||
|
||||
@ -29,8 +29,6 @@ const (
|
||||
|
||||
// Dev points to the root directory for device nodes.
|
||||
Dev = "/dev/"
|
||||
// DevShm is the place for POSIX shared memory segments, as created via shm_open(3).
|
||||
DevShm = "/dev/shm/"
|
||||
// Proc points to a virtual kernel file system exposing the process list and other functionality.
|
||||
Proc = "/proc/"
|
||||
// ProcSys points to a hierarchy below /proc/ that exposes a number of kernel tunables.
|
||||
|
||||
@ -9,8 +9,7 @@
|
||||
|
||||
#define LEN(arr) (sizeof(arr) / sizeof((arr)[0]))
|
||||
|
||||
int32_t hakurei_scmp_make_filter(
|
||||
int *ret_p, uintptr_t allocate_p,
|
||||
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) {
|
||||
@ -73,9 +72,11 @@ int32_t hakurei_scmp_make_filter(
|
||||
assert(rule->m_errno == EPERM || rule->m_errno == ENOSYS);
|
||||
|
||||
if (rule->arg)
|
||||
*ret_p = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(rule->m_errno), rule->syscall, 1, *rule->arg);
|
||||
*ret_p = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(rule->m_errno),
|
||||
rule->syscall, 1, *rule->arg);
|
||||
else
|
||||
*ret_p = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(rule->m_errno), rule->syscall, 0);
|
||||
*ret_p = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(rule->m_errno),
|
||||
rule->syscall, 0);
|
||||
|
||||
if (*ret_p == -EFAULT) {
|
||||
res = 4;
|
||||
@ -92,17 +93,22 @@ int32_t hakurei_scmp_make_filter(
|
||||
last_allowed_family = -1;
|
||||
for (i = 0; i < LEN(socket_family_allowlist); i++) {
|
||||
if (socket_family_allowlist[i].flags_mask != 0 &&
|
||||
(socket_family_allowlist[i].flags_mask & flags) != socket_family_allowlist[i].flags_mask)
|
||||
(socket_family_allowlist[i].flags_mask & flags) !=
|
||||
socket_family_allowlist[i].flags_mask)
|
||||
continue;
|
||||
|
||||
for (disallowed = last_allowed_family + 1; disallowed < socket_family_allowlist[i].family; disallowed++) {
|
||||
for (disallowed = last_allowed_family + 1;
|
||||
disallowed < socket_family_allowlist[i].family; disallowed++) {
|
||||
/* Blocklist the in-between valid families */
|
||||
seccomp_rule_add_exact(ctx, SCMP_ACT_ERRNO(EAFNOSUPPORT), SCMP_SYS(socket), 1, SCMP_A0(SCMP_CMP_EQ, disallowed));
|
||||
seccomp_rule_add_exact(ctx, SCMP_ACT_ERRNO(EAFNOSUPPORT),
|
||||
SCMP_SYS(socket), 1,
|
||||
SCMP_A0(SCMP_CMP_EQ, disallowed));
|
||||
}
|
||||
last_allowed_family = socket_family_allowlist[i].family;
|
||||
}
|
||||
/* Blocklist the rest */
|
||||
seccomp_rule_add_exact(ctx, SCMP_ACT_ERRNO(EAFNOSUPPORT), SCMP_SYS(socket), 1, SCMP_A0(SCMP_CMP_GE, last_allowed_family + 1));
|
||||
seccomp_rule_add_exact(ctx, SCMP_ACT_ERRNO(EAFNOSUPPORT), SCMP_SYS(socket), 1,
|
||||
SCMP_A0(SCMP_CMP_GE, last_allowed_family + 1));
|
||||
|
||||
if (allocate_p == 0) {
|
||||
*ret_p = seccomp_load(ctx);
|
||||
|
||||
@ -19,8 +19,7 @@ struct hakurei_syscall_rule {
|
||||
};
|
||||
|
||||
extern void *hakurei_scmp_allocate(uintptr_t f, size_t len);
|
||||
int32_t hakurei_scmp_make_filter(
|
||||
int *ret_p, uintptr_t allocate_p,
|
||||
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);
|
||||
@ -7,10 +7,8 @@ import (
|
||||
"hakurei.app/container/stub"
|
||||
)
|
||||
|
||||
// Made available here to check panic recovery behaviour.
|
||||
//
|
||||
//go:linkname handleExitNew hakurei.app/container/stub.handleExitNew
|
||||
func handleExitNew(t testing.TB)
|
||||
func handleExitNew(_ testing.TB)
|
||||
|
||||
// overrideTFailNow overrides the Fail and FailNow method.
|
||||
type overrideTFailNow struct {
|
||||
|
||||
6
dist/release.sh
vendored
6
dist/release.sh
vendored
@ -10,9 +10,9 @@ cp -rv "dist/comp" "${out}"
|
||||
|
||||
go generate ./...
|
||||
go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w -buildid= -extldflags '-static'
|
||||
-X hakurei.app/internal/info.buildVersion=${VERSION}
|
||||
-X hakurei.app/internal/info.hakureiPath=/usr/bin/hakurei
|
||||
-X hakurei.app/internal/info.hsuPath=/usr/bin/hsu
|
||||
-X hakurei.app/internal.buildVersion=${VERSION}
|
||||
-X hakurei.app/internal.hakureiPath=/usr/bin/hakurei
|
||||
-X hakurei.app/internal.hsuPath=/usr/bin/hsu
|
||||
-X main.hakureiPath=/usr/bin/hakurei" ./...
|
||||
|
||||
rm -f "./${out}.tar.gz" && tar -C dist -czf "${out}.tar.gz" "${pname}"
|
||||
|
||||
@ -114,7 +114,7 @@
|
||||
inherit (pkgs)
|
||||
# passthru.buildInputs
|
||||
go
|
||||
clang
|
||||
gcc
|
||||
|
||||
# nativeBuildInputs
|
||||
pkg-config
|
||||
@ -129,10 +129,6 @@
|
||||
zstd
|
||||
gnutar
|
||||
coreutils
|
||||
|
||||
# for check
|
||||
util-linux
|
||||
nettools
|
||||
;
|
||||
};
|
||||
hsu = pkgs.callPackage ./cmd/hsu/package.nix { inherit (self.packages.${system}) hakurei; };
|
||||
@ -148,7 +144,7 @@
|
||||
&& chmod -R +w .
|
||||
|
||||
export HAKUREI_VERSION="v${hakurei.version}"
|
||||
CC="clang -O3 -Werror" ./dist/release.sh && mkdir $out && cp -v "dist/hakurei-$HAKUREI_VERSION.tar.gz"* $out
|
||||
./dist/release.sh && mkdir $out && cp -v "dist/hakurei-$HAKUREI_VERSION.tar.gz"* $out
|
||||
'';
|
||||
}
|
||||
);
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/internal/helper"
|
||||
"hakurei.app/helper"
|
||||
)
|
||||
|
||||
func TestArgsString(t *testing.T) {
|
||||
@ -10,7 +10,7 @@ import (
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/internal/helper/proc"
|
||||
"hakurei.app/helper/proc"
|
||||
)
|
||||
|
||||
// NewDirect initialises a new direct Helper instance with wt as the null-terminated argument writer.
|
||||
@ -9,7 +9,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/internal/helper"
|
||||
"hakurei.app/helper"
|
||||
)
|
||||
|
||||
func TestCmd(t *testing.T) {
|
||||
@ -11,7 +11,7 @@ import (
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/internal/helper/proc"
|
||||
"hakurei.app/helper/proc"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
@ -9,7 +9,7 @@ import (
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/internal/helper"
|
||||
"hakurei.app/helper"
|
||||
)
|
||||
|
||||
func TestContainer(t *testing.T) {
|
||||
@ -1,73 +0,0 @@
|
||||
// Package helper exposes the internal/helper package.
|
||||
//
|
||||
// Deprecated: This package will be removed in 0.4.
|
||||
package helper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
_ "unsafe" // for go:linkname
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/internal/helper"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
//go:linkname WaitDelay hakurei.app/internal/helper.WaitDelay
|
||||
var WaitDelay time.Duration
|
||||
|
||||
const (
|
||||
// HakureiHelper is set to 1 when args fd is enabled and 0 otherwise.
|
||||
HakureiHelper = helper.HakureiHelper
|
||||
// HakureiStatus is set to 1 when stat fd is enabled and 0 otherwise.
|
||||
HakureiStatus = helper.HakureiStatus
|
||||
)
|
||||
|
||||
type Helper = helper.Helper
|
||||
|
||||
// NewCheckedArgs returns a checked null-terminated argument writer for a copy of args.
|
||||
//
|
||||
//go:linkname NewCheckedArgs hakurei.app/internal/helper.NewCheckedArgs
|
||||
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.
|
||||
//
|
||||
//go:linkname MustNewCheckedArgs hakurei.app/internal/helper.MustNewCheckedArgs
|
||||
func MustNewCheckedArgs(args ...string) io.WriterTo
|
||||
|
||||
// NewDirect initialises a new direct Helper instance with wt as the null-terminated argument writer.
|
||||
// Function argF returns an array of arguments passed directly to the child process.
|
||||
//
|
||||
//go:linkname NewDirect hakurei.app/internal/helper.NewDirect
|
||||
func NewDirect(
|
||||
ctx context.Context,
|
||||
name string,
|
||||
wt io.WriterTo,
|
||||
stat bool,
|
||||
argF func(argsFd, statFd int) []string,
|
||||
cmdF func(cmd *exec.Cmd),
|
||||
extraFiles []*os.File,
|
||||
) Helper
|
||||
|
||||
// New initialises a Helper instance with wt as the null-terminated argument writer.
|
||||
//
|
||||
//go:linkname New hakurei.app/internal/helper.New
|
||||
func New(
|
||||
ctx context.Context,
|
||||
msg message.Msg,
|
||||
pathname *check.Absolute, name string,
|
||||
wt io.WriterTo,
|
||||
stat bool,
|
||||
argF func(argsFd, statFd int) []string,
|
||||
cmdF func(z *container.Container),
|
||||
extraFiles []*os.File,
|
||||
) Helper
|
||||
|
||||
// InternalHelperStub is an internal function but exported because it is cross-package;
|
||||
// it is part of the implementation of the helper stub.
|
||||
func InternalHelperStub() { helper.InternalHelperStub() }
|
||||
@ -8,7 +8,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"hakurei.app/internal/helper/proc"
|
||||
"hakurei.app/helper/proc"
|
||||
)
|
||||
|
||||
var WaitDelay = 2 * time.Second
|
||||
@ -13,7 +13,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hakurei.app/internal/helper"
|
||||
"hakurei.app/helper"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -1,63 +0,0 @@
|
||||
// Deprecated: This package will be removed in 0.4.
|
||||
package proc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
_ "unsafe" // for go:linkname
|
||||
|
||||
"hakurei.app/internal/helper/proc"
|
||||
)
|
||||
|
||||
//go:linkname FulfillmentTimeout hakurei.app/internal/helper/proc.FulfillmentTimeout
|
||||
var FulfillmentTimeout time.Duration
|
||||
|
||||
// A File is an extra file with deferred initialisation.
|
||||
type File = proc.File
|
||||
|
||||
// ExtraFilesPre is a linked list storing addresses of [os.File].
|
||||
type ExtraFilesPre = proc.ExtraFilesPre
|
||||
|
||||
// Fulfill calls the [File.Fulfill] method on all files, starts cmd and blocks until all fulfillment completes.
|
||||
//
|
||||
//go:linkname Fulfill hakurei.app/internal/helper/proc.Fulfill
|
||||
func Fulfill(ctx context.Context,
|
||||
v *[]*os.File, start func() error,
|
||||
files []File, extraFiles *ExtraFilesPre,
|
||||
) (err error)
|
||||
|
||||
// InitFile initialises f as part of the slice extraFiles points to,
|
||||
// and returns its final fd value.
|
||||
//
|
||||
//go:linkname InitFile hakurei.app/internal/helper/proc.InitFile
|
||||
func InitFile(f File, extraFiles *ExtraFilesPre) (fd uintptr)
|
||||
|
||||
// BaseFile implements the Init method of the File interface and provides indirect access to extra file state.
|
||||
type BaseFile = proc.BaseFile
|
||||
|
||||
//go:linkname ExtraFile hakurei.app/internal/helper/proc.ExtraFile
|
||||
func ExtraFile(cmd *exec.Cmd, f *os.File) (fd uintptr)
|
||||
|
||||
//go:linkname ExtraFileSlice hakurei.app/internal/helper/proc.ExtraFileSlice
|
||||
func ExtraFileSlice(extraFiles *[]*os.File, f *os.File) (fd uintptr)
|
||||
|
||||
// NewWriterTo returns a [File] that receives content from wt on fulfillment.
|
||||
//
|
||||
//go:linkname NewWriterTo hakurei.app/internal/helper/proc.NewWriterTo
|
||||
func NewWriterTo(wt io.WriterTo) File
|
||||
|
||||
// NewStat returns a [File] implementing the behaviour
|
||||
// of the receiving end of xdg-dbus-proxy stat fd.
|
||||
//
|
||||
//go:linkname NewStat hakurei.app/internal/helper/proc.NewStat
|
||||
func NewStat(s *io.Closer) File
|
||||
|
||||
var (
|
||||
//go:linkname ErrStatFault hakurei.app/internal/helper/proc.ErrStatFault
|
||||
ErrStatFault error
|
||||
//go:linkname ErrStatRead hakurei.app/internal/helper/proc.ErrStatRead
|
||||
ErrStatRead error
|
||||
)
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/internal/helper"
|
||||
"hakurei.app/helper"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) { container.TryArgv0(nil); helper.InternalHelperStub(); os.Exit(m.Run()) }
|
||||
@ -6,13 +6,11 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
_ "unsafe" // for go:linkname
|
||||
_ "unsafe"
|
||||
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
// Made available here to check time encoding behaviour of [hst.ID].
|
||||
//
|
||||
//go:linkname newInstanceID hakurei.app/hst.newInstanceID
|
||||
func newInstanceID(id *hst.ID, p uint64) error
|
||||
|
||||
|
||||
@ -1,90 +0,0 @@
|
||||
#include "libacl-helper.h"
|
||||
#include <acl/libacl.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/acl.h>
|
||||
|
||||
int hakurei_acl_update_file_by_uid(const char *path_p, uid_t uid,
|
||||
acl_perm_t *perms, size_t plen) {
|
||||
int ret;
|
||||
bool v;
|
||||
int i;
|
||||
acl_t acl;
|
||||
acl_entry_t entry;
|
||||
acl_tag_t tag_type;
|
||||
void *qualifier_p;
|
||||
acl_permset_t permset;
|
||||
|
||||
ret = -1; /* acl_get_file */
|
||||
acl = acl_get_file(path_p, ACL_TYPE_ACCESS);
|
||||
if (acl == NULL)
|
||||
goto out;
|
||||
|
||||
/* prune entries by uid */
|
||||
for (i = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); i == 1;
|
||||
i = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry)) {
|
||||
ret = -2; /* acl_get_tag_type */
|
||||
if (acl_get_tag_type(entry, &tag_type) != 0)
|
||||
goto out;
|
||||
if (tag_type != ACL_USER)
|
||||
continue;
|
||||
|
||||
ret = -3; /* acl_get_qualifier */
|
||||
qualifier_p = acl_get_qualifier(entry);
|
||||
if (qualifier_p == NULL)
|
||||
goto out;
|
||||
v = *(uid_t *)qualifier_p == uid;
|
||||
acl_free(qualifier_p);
|
||||
|
||||
if (!v)
|
||||
continue;
|
||||
|
||||
ret = -4; /* acl_delete_entry */
|
||||
if (acl_delete_entry(acl, entry) != 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (plen == 0)
|
||||
goto set;
|
||||
|
||||
ret = -5; /* acl_create_entry */
|
||||
if (acl_create_entry(&acl, &entry) != 0)
|
||||
goto out;
|
||||
|
||||
ret = -6; /* acl_get_permset */
|
||||
if (acl_get_permset(entry, &permset) != 0)
|
||||
goto out;
|
||||
|
||||
ret = -7; /* acl_add_perm */
|
||||
for (i = 0; i < plen; i++) {
|
||||
if (acl_add_perm(permset, perms[i]) != 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = -8; /* acl_set_tag_type */
|
||||
if (acl_set_tag_type(entry, ACL_USER) != 0)
|
||||
goto out;
|
||||
|
||||
ret = -9; /* acl_set_qualifier */
|
||||
if (acl_set_qualifier(entry, (void *)&uid) != 0)
|
||||
goto out;
|
||||
|
||||
set:
|
||||
ret = -10; /* acl_calc_mask */
|
||||
if (acl_calc_mask(&acl) != 0)
|
||||
goto out;
|
||||
|
||||
ret = -11; /* acl_valid */
|
||||
if (acl_valid(acl) != 0)
|
||||
goto out;
|
||||
|
||||
ret = -12; /* acl_set_file */
|
||||
if (acl_set_file(path_p, ACL_TYPE_ACCESS, acl) == 0)
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
free((void *)path_p);
|
||||
if (acl != NULL)
|
||||
acl_free((void *)acl);
|
||||
return ret;
|
||||
}
|
||||
@ -14,9 +14,9 @@ import (
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/container/std"
|
||||
"hakurei.app/internal/dbus"
|
||||
"hakurei.app/internal/info"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
// osFile represents [os.File].
|
||||
@ -156,7 +156,7 @@ func (direct) seccompLoad(rules []std.NativeRule, flags seccomp.ExportFlag) erro
|
||||
return seccomp.Load(rules, flags)
|
||||
}
|
||||
|
||||
func (direct) mustHsuPath() *check.Absolute { return info.MustHsuPath() }
|
||||
func (direct) mustHsuPath() *check.Absolute { return internal.MustHsuPath() }
|
||||
|
||||
func (direct) dbusAddress() (session, system string) { return dbus.Address() }
|
||||
|
||||
|
||||
@ -24,8 +24,8 @@ import (
|
||||
"hakurei.app/container/std"
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/system"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system"
|
||||
)
|
||||
|
||||
// call initialises a [stub.Call].
|
||||
|
||||
@ -8,8 +8,8 @@ import (
|
||||
"os/user"
|
||||
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/system"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system"
|
||||
)
|
||||
|
||||
func newWithMessage(msg string) error { return newWithMessageError(msg, os.ErrInvalid) }
|
||||
|
||||
@ -12,8 +12,6 @@ import (
|
||||
|
||||
// IsPollDescriptor reports whether fd is the descriptor being used by the poller.
|
||||
//
|
||||
// Made available here to determine and reject impossible fd.
|
||||
//
|
||||
//go:linkname IsPollDescriptor internal/poll.IsPollDescriptor
|
||||
func IsPollDescriptor(fd uintptr) bool
|
||||
|
||||
@ -21,10 +21,10 @@ import (
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/container/std"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/internal/dbus"
|
||||
"hakurei.app/internal/system"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/acl"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
func TestOutcomeMain(t *testing.T) {
|
||||
@ -141,7 +141,7 @@ func TestOutcomeMain(t *testing.T) {
|
||||
Proc(fhs.AbsProc).
|
||||
Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755).
|
||||
Bind(fhs.AbsDev, fhs.AbsDev, std.BindWritable|std.BindDevice).
|
||||
Tmpfs(fhs.AbsDevShm, 0, 01777).
|
||||
Tmpfs(fhs.AbsDev.Append("shm"), 0, 01777).
|
||||
|
||||
// spRuntimeOp
|
||||
Tmpfs(fhs.AbsRunUser, 1<<12, 0755).
|
||||
@ -243,7 +243,7 @@ func TestOutcomeMain(t *testing.T) {
|
||||
Proc(m("/proc/")).
|
||||
Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
|
||||
DevWritable(m("/dev/"), true).
|
||||
Tmpfs(m("/dev/shm/"), 0, 01777).
|
||||
Tmpfs(m("/dev/shm"), 0, 01777).
|
||||
Tmpfs(m("/run/user/"), 4096, 0755).
|
||||
Bind(m("/tmp/hakurei.0/runtime/0"), m("/run/user/65534"), std.BindWritable).
|
||||
Bind(m("/tmp/hakurei.0/tmpdir/0"), m("/tmp/"), std.BindWritable).
|
||||
@ -412,7 +412,7 @@ func TestOutcomeMain(t *testing.T) {
|
||||
Proc(m("/proc/")).
|
||||
Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
|
||||
DevWritable(m("/dev/"), true).
|
||||
Tmpfs(m("/dev/shm/"), 0, 01777).
|
||||
Tmpfs(m("/dev/shm"), 0, 01777).
|
||||
Tmpfs(m("/run/user/"), 4096, 0755).
|
||||
Bind(m("/tmp/hakurei.0/runtime/9"), m("/run/user/65534"), std.BindWritable).
|
||||
Bind(m("/tmp/hakurei.0/tmpdir/9"), m("/tmp/"), std.BindWritable).
|
||||
@ -558,7 +558,7 @@ func TestOutcomeMain(t *testing.T) {
|
||||
Proc(m("/proc/")).
|
||||
Tmpfs(hst.AbsPrivateTmp, 4096, 0755).
|
||||
DevWritable(m("/dev/"), true).
|
||||
Tmpfs(m("/dev/shm/"), 0, 01777).
|
||||
Tmpfs(m("/dev/shm"), 0, 01777).
|
||||
Tmpfs(m("/run/user/"), 4096, 0755).
|
||||
Bind(m("/tmp/hakurei.0/runtime/1"), m("/run/user/1971"), std.BindWritable).
|
||||
Bind(m("/tmp/hakurei.0/tmpdir/1"), m("/tmp/"), std.BindWritable).
|
||||
@ -9,10 +9,10 @@ import (
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/internal/env"
|
||||
"hakurei.app/internal/system"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/acl"
|
||||
)
|
||||
|
||||
// envAllocSize is the initial size of the env map pre-allocated when the configured env map is nil.
|
||||
|
||||
@ -16,10 +16,10 @@ import (
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/info"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/store"
|
||||
"hakurei.app/internal/system"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -39,7 +39,7 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
|
||||
}
|
||||
|
||||
// read comp value early for early failure
|
||||
hsuPath := info.MustHsuPath()
|
||||
hsuPath := internal.MustHsuPath()
|
||||
|
||||
const (
|
||||
// transitions to processCommit, or processFinal on failure
|
||||
|
||||
@ -38,7 +38,7 @@ static void hakurei_shim_sigaction(int sig, siginfo_t *si, void *ucontext) {
|
||||
|
||||
void hakurei_shim_setup_cont_signal(pid_t ppid, int fd) {
|
||||
if (hakurei_shim_param_ppid != -1 || hakurei_shim_fd != -1)
|
||||
*(volatile int *)NULL = 0; /* unreachable */
|
||||
*(int *)NULL = 0; /* unreachable */
|
||||
|
||||
struct sigaction new_action = {0}, old_action = {0};
|
||||
if (sigaction(SIGCONT, NULL, &old_action) != 0)
|
||||
|
||||
@ -66,7 +66,7 @@ func TestShimEntrypoint(t *testing.T) {
|
||||
Proc(fhs.AbsProc).
|
||||
Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755).
|
||||
Bind(fhs.AbsDev, fhs.AbsDev, std.BindWritable|std.BindDevice).
|
||||
Tmpfs(fhs.AbsDevShm, 0, 01777).
|
||||
Tmpfs(fhs.AbsDev.Append("shm"), 0, 01777).
|
||||
|
||||
// spRuntimeOp
|
||||
Tmpfs(fhs.AbsRunUser, 1<<12, 0755).
|
||||
|
||||
@ -16,11 +16,11 @@ import (
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/container/std"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/internal/dbus"
|
||||
"hakurei.app/internal/system"
|
||||
"hakurei.app/internal/validate"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/acl"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
const varRunNscd = fhs.Var + "run/nscd"
|
||||
@ -116,7 +116,7 @@ func (s *spParamsOp) toContainer(state *outcomeStateParams) error {
|
||||
state.params.Bind(fhs.AbsDev, fhs.AbsDev, std.BindWritable|std.BindDevice)
|
||||
}
|
||||
// /dev is mounted readonly later on, this prevents /dev/shm from going readonly with it
|
||||
state.params.Tmpfs(fhs.AbsDevShm, 0, 01777)
|
||||
state.params.Tmpfs(fhs.AbsDev.Append("shm"), 0, 01777)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -14,9 +14,9 @@ import (
|
||||
"hakurei.app/container/std"
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/internal/dbus"
|
||||
"hakurei.app/internal/system"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/acl"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
func TestSpParamsOp(t *testing.T) {
|
||||
@ -72,7 +72,7 @@ func TestSpParamsOp(t *testing.T) {
|
||||
Root(m("/var/lib/hakurei/base/org.debian"), std.BindWritable).
|
||||
Proc(fhs.AbsProc).Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755).
|
||||
DevWritable(fhs.AbsDev, true).
|
||||
Tmpfs(fhs.AbsDevShm, 0, 01777),
|
||||
Tmpfs(fhs.AbsDev.Append("shm"), 0, 01777),
|
||||
}, paramsWantEnv(config, map[string]string{
|
||||
"TERM": "xterm",
|
||||
}, func(t *testing.T, state *outcomeStateParams) {
|
||||
@ -110,7 +110,7 @@ func TestSpParamsOp(t *testing.T) {
|
||||
Root(m("/var/lib/hakurei/base/org.debian"), std.BindWritable).
|
||||
Proc(fhs.AbsProc).Tmpfs(hst.AbsPrivateTmp, 1<<12, 0755).
|
||||
Bind(fhs.AbsDev, fhs.AbsDev, std.BindWritable|std.BindDevice).
|
||||
Tmpfs(fhs.AbsDevShm, 0, 01777),
|
||||
Tmpfs(fhs.AbsDev.Append("shm"), 0, 01777),
|
||||
}, paramsWantEnv(config, map[string]string{
|
||||
"TERM": "xterm",
|
||||
}, func(t *testing.T, state *outcomeStateParams) {
|
||||
|
||||
@ -5,8 +5,8 @@ import (
|
||||
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/internal/dbus"
|
||||
"hakurei.app/system/acl"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
func init() { gob.Register(new(spDBusOp)) }
|
||||
|
||||
@ -6,12 +6,12 @@ import (
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/helper"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/internal/dbus"
|
||||
"hakurei.app/internal/helper"
|
||||
"hakurei.app/internal/system"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/acl"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
func TestSpDBusOp(t *testing.T) {
|
||||
|
||||
@ -11,8 +11,8 @@ import (
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/internal/system"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/acl"
|
||||
)
|
||||
|
||||
func TestSpPulseOp(t *testing.T) {
|
||||
|
||||
@ -7,8 +7,8 @@ import (
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/container/std"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/internal/system"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/acl"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@ -8,8 +8,8 @@ import (
|
||||
"hakurei.app/container/std"
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/internal/system"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/acl"
|
||||
)
|
||||
|
||||
func TestSpRuntimeOp(t *testing.T) {
|
||||
|
||||
@ -7,8 +7,8 @@ import (
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/container/std"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/internal/system"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/acl"
|
||||
)
|
||||
|
||||
func init() { gob.Register(spTmpdirOp{}) }
|
||||
|
||||
@ -8,8 +8,8 @@ import (
|
||||
"hakurei.app/container/std"
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/internal/system"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/acl"
|
||||
)
|
||||
|
||||
func TestSpTmpdirOp(t *testing.T) {
|
||||
|
||||
@ -5,8 +5,8 @@ import (
|
||||
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/internal/wayland"
|
||||
"hakurei.app/system/acl"
|
||||
"hakurei.app/system/wayland"
|
||||
)
|
||||
|
||||
func init() { gob.Register(new(spWaylandOp)) }
|
||||
@ -25,8 +25,8 @@ func (s *spWaylandOp) toSystem(state *outcomeStateSys) error {
|
||||
|
||||
// outer wayland socket (usually `/run/user/%d/wayland-%d`)
|
||||
var socketPath *check.Absolute
|
||||
if name, ok := state.k.lookupEnv(wayland.Display); !ok {
|
||||
state.msg.Verbose(wayland.Display + " is not set, assuming " + wayland.FallbackName)
|
||||
if name, ok := state.k.lookupEnv(wayland.WaylandDisplay); !ok {
|
||||
state.msg.Verbose(wayland.WaylandDisplay + " is not set, assuming " + wayland.FallbackName)
|
||||
socketPath = state.sc.RuntimePath.Append(wayland.FallbackName)
|
||||
} else if a, err := check.NewAbs(name); err != nil {
|
||||
socketPath = state.sc.RuntimePath.Append(name)
|
||||
@ -53,7 +53,7 @@ func (s *spWaylandOp) toSystem(state *outcomeStateSys) error {
|
||||
|
||||
func (s *spWaylandOp) toContainer(state *outcomeStateParams) error {
|
||||
innerPath := state.runtimeDir.Append(wayland.FallbackName)
|
||||
state.env[wayland.Display] = wayland.FallbackName
|
||||
state.env[wayland.WaylandDisplay] = wayland.FallbackName
|
||||
if s.SocketPath == nil {
|
||||
state.params.Bind(state.instancePath().Append("wayland"), innerPath, 0)
|
||||
} else {
|
||||
|
||||
@ -6,9 +6,9 @@ import (
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/internal/system"
|
||||
"hakurei.app/internal/wayland"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/acl"
|
||||
"hakurei.app/system/wayland"
|
||||
)
|
||||
|
||||
func TestSpWaylandOp(t *testing.T) {
|
||||
@ -47,7 +47,7 @@ func TestSpWaylandOp(t *testing.T) {
|
||||
Ops: new(container.Ops).
|
||||
Bind(m(wantInstancePrefix+"/wayland"), m("/run/user/1000/wayland-0"), 0),
|
||||
}, paramsWantEnv(config, map[string]string{
|
||||
wayland.Display: wayland.FallbackName,
|
||||
wayland.WaylandDisplay: wayland.FallbackName,
|
||||
}, nil), nil},
|
||||
|
||||
{"success direct", func(isShim, _ bool) outcomeOp {
|
||||
@ -75,7 +75,7 @@ func TestSpWaylandOp(t *testing.T) {
|
||||
Ops: new(container.Ops).
|
||||
Bind(m("/proc/nonexistent/wayland"), m("/run/user/1000/wayland-0"), 0),
|
||||
}, paramsWantEnv(config, map[string]string{
|
||||
wayland.Display: wayland.FallbackName,
|
||||
wayland.WaylandDisplay: wayland.FallbackName,
|
||||
}, nil), nil},
|
||||
|
||||
{"success", func(bool, bool) outcomeOp {
|
||||
@ -98,7 +98,7 @@ func TestSpWaylandOp(t *testing.T) {
|
||||
Ops: new(container.Ops).
|
||||
Bind(m(wantInstancePrefix+"/wayland"), m("/run/user/1000/wayland-0"), 0),
|
||||
}, paramsWantEnv(config, map[string]string{
|
||||
wayland.Display: wayland.FallbackName,
|
||||
wayland.WaylandDisplay: wayland.FallbackName,
|
||||
}, nil), nil},
|
||||
})
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ import (
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/fhs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/system/acl"
|
||||
)
|
||||
|
||||
var absX11SocketDir = fhs.AbsTmp.Append(".X11-unix")
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/system/acl"
|
||||
)
|
||||
|
||||
func TestSpX11Op(t *testing.T) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package info
|
||||
package internal
|
||||
|
||||
import (
|
||||
"log"
|
||||
@ -1,4 +1,4 @@
|
||||
package info
|
||||
package internal
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
@ -27,7 +27,7 @@ func TestEntryData(t *testing.T) {
|
||||
return buf.String()
|
||||
}
|
||||
}
|
||||
templateStateGob := mustEncodeGob(NewTemplateState())
|
||||
templateStateGob := mustEncodeGob(newTemplateState())
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
@ -45,11 +45,11 @@ func TestEntryData(t *testing.T) {
|
||||
Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||
Msg: "invalid configuration"}},
|
||||
|
||||
{"inconsistent enablement", "\x00\xff\xca\xfe\x00\x00\xff\x00" + templateStateGob, NewTemplateState(), &hst.AppError{
|
||||
{"inconsistent enablement", "\x00\xff\xca\xfe\x00\x00\xff\x00" + templateStateGob, newTemplateState(), &hst.AppError{
|
||||
Step: "validate state enablement", Err: os.ErrInvalid,
|
||||
Msg: "state entry aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa has unexpected enablement byte 0xd, 0xff"}},
|
||||
|
||||
{"template", "\x00\xff\xca\xfe\x00\x00\x0d\xf2" + templateStateGob, NewTemplateState(), nil},
|
||||
{"template", "\x00\xff\xca\xfe\x00\x00\x0d\xf2" + templateStateGob, newTemplateState(), nil},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@ -105,7 +105,7 @@ func TestEntryData(t *testing.T) {
|
||||
|
||||
t.Run("encode fault", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
s := NewTemplateState()
|
||||
s := newTemplateState()
|
||||
|
||||
t.Run("gob", func(t *testing.T) {
|
||||
var want = &hst.AppError{Step: "encode state body", Err: stub.UniqueError(0xcafe)}
|
||||
@ -123,8 +123,8 @@ func TestEntryData(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// NewTemplateState returns the address of a new template [hst.State] struct.
|
||||
func NewTemplateState() *hst.State {
|
||||
// newTemplateState returns the address of a new template [hst.State] struct.
|
||||
func newTemplateState() *hst.State {
|
||||
return &hst.State{
|
||||
ID: hst.ID(bytes.Repeat([]byte{0xaa}, len(hst.ID{}))),
|
||||
PID: 0xcafe,
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
_ "unsafe" // for go:linkname
|
||||
_ "unsafe"
|
||||
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/stub"
|
||||
@ -18,23 +18,18 @@ import (
|
||||
"hakurei.app/internal/store"
|
||||
)
|
||||
|
||||
// Made available here for direct validation of state entry files.
|
||||
//
|
||||
//go:linkname newTemplateState hakurei.app/internal/store.newTemplateState
|
||||
func newTemplateState() *hst.State
|
||||
|
||||
//go:linkname entryDecode hakurei.app/internal/store.entryDecode
|
||||
func entryDecode(r io.Reader, p *hst.State) (hst.Enablement, error)
|
||||
|
||||
// Made available here for direct access to known segment handles.
|
||||
//
|
||||
//go:linkname newHandle hakurei.app/internal/store.newHandle
|
||||
func newHandle(base *check.Absolute, identity int) *store.Handle
|
||||
|
||||
// Made available here to check open error handling behaviour.
|
||||
//
|
||||
//go:linkname open hakurei.app/internal/store.(*EntryHandle).open
|
||||
func open(eh *store.EntryHandle, flag int, perm os.FileMode) (*os.File, error)
|
||||
|
||||
// Made available here to check the saveload cycle.
|
||||
//
|
||||
//go:linkname save hakurei.app/internal/store.(*EntryHandle).save
|
||||
func save(eh *store.EntryHandle, state *hst.State) error
|
||||
|
||||
@ -96,9 +91,9 @@ func TestStateEntryHandle(t *testing.T) {
|
||||
t.Run("saveload", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
eh := store.EntryHandle{Pathname: check.MustAbs(t.TempDir()).Append("entry"),
|
||||
ID: store.NewTemplateState().ID}
|
||||
ID: newTemplateState().ID}
|
||||
|
||||
if err := save(&eh, store.NewTemplateState()); err != nil {
|
||||
if err := save(&eh, newTemplateState()); err != nil {
|
||||
t.Fatalf("save: error = %v", err)
|
||||
}
|
||||
|
||||
@ -117,7 +112,7 @@ func TestStateEntryHandle(t *testing.T) {
|
||||
t.Fatal(f.Close())
|
||||
}
|
||||
|
||||
if want := store.NewTemplateState(); !reflect.DeepEqual(&got, want) {
|
||||
if want := newTemplateState(); !reflect.DeepEqual(&got, want) {
|
||||
t.Errorf("entryDecode: %#v, want %#v", &got, want)
|
||||
}
|
||||
})
|
||||
@ -127,7 +122,7 @@ func TestStateEntryHandle(t *testing.T) {
|
||||
|
||||
if et, err := eh.Load(nil); err != nil {
|
||||
t.Fatalf("load: error = %v", err)
|
||||
} else if want := store.NewTemplateState().Enablements.Unwrap(); et != want {
|
||||
} else if want := newTemplateState().Enablements.Unwrap(); et != want {
|
||||
t.Errorf("load: et = %x, want %x", et, want)
|
||||
}
|
||||
})
|
||||
@ -138,7 +133,7 @@ func TestStateEntryHandle(t *testing.T) {
|
||||
var got hst.State
|
||||
if _, err := eh.Load(&got); err != nil {
|
||||
t.Fatalf("load: error = %v", err)
|
||||
} else if want := store.NewTemplateState(); !reflect.DeepEqual(&got, want) {
|
||||
} else if want := newTemplateState(); !reflect.DeepEqual(&got, want) {
|
||||
t.Errorf("load: %#v, want %#v", &got, want)
|
||||
}
|
||||
})
|
||||
|
||||
@ -12,15 +12,13 @@ import (
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
_ "unsafe" // for go:linkname
|
||||
_ "unsafe"
|
||||
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/store"
|
||||
)
|
||||
|
||||
// Made available here to check bigLock error handling behaviour.
|
||||
//
|
||||
//go:linkname bigLock hakurei.app/internal/store.(*Store).bigLock
|
||||
func bigLock(s *store.Store) (unlock func(), err error)
|
||||
|
||||
|
||||
@ -1,81 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/internal/wayland"
|
||||
)
|
||||
|
||||
// Wayland maintains a wayland socket with security-context-v1 attached via [wayland].
|
||||
// The socket stops accepting connections once the pipe referred to by sync is closed.
|
||||
// The socket is pathname only and is destroyed on revert.
|
||||
func (sys *I) Wayland(dst, src *check.Absolute, appID, instanceID string) *I {
|
||||
sys.ops = append(sys.ops, &waylandOp{nil,
|
||||
dst, src, appID, instanceID})
|
||||
return sys
|
||||
}
|
||||
|
||||
// waylandOp implements [I.Wayland].
|
||||
type waylandOp struct {
|
||||
ctx *wayland.SecurityContext
|
||||
dst, src *check.Absolute
|
||||
appID, instanceID string
|
||||
}
|
||||
|
||||
func (w *waylandOp) Type() hst.Enablement { return Process }
|
||||
|
||||
func (w *waylandOp) apply(sys *I) (err error) {
|
||||
if w.ctx, err = sys.waylandNew(w.src, w.dst, w.appID, w.instanceID); err != nil {
|
||||
return newOpError("wayland", err, false)
|
||||
} else {
|
||||
sys.msg.Verbosef("wayland pathname socket on %q via %q", w.dst, w.src)
|
||||
|
||||
if err = sys.chmod(w.dst.String(), 0); err != nil {
|
||||
if closeErr := w.ctx.Close(); closeErr != nil {
|
||||
return newOpError("wayland", errors.Join(err, closeErr), false)
|
||||
}
|
||||
return newOpError("wayland", err, false)
|
||||
}
|
||||
|
||||
if err = sys.aclUpdate(w.dst.String(), sys.uid, acl.Read, acl.Write, acl.Execute); err != nil {
|
||||
if closeErr := w.ctx.Close(); closeErr != nil {
|
||||
return newOpError("wayland", errors.Join(err, closeErr), false)
|
||||
}
|
||||
return newOpError("wayland", err, false)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (w *waylandOp) revert(sys *I, _ *Criteria) error {
|
||||
var (
|
||||
hangupErr error
|
||||
removeErr error
|
||||
)
|
||||
|
||||
sys.msg.Verbosef("hanging up wayland socket on %q", w.dst)
|
||||
if w.ctx != nil {
|
||||
hangupErr = w.ctx.Close()
|
||||
}
|
||||
if err := sys.remove(w.dst.String()); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
removeErr = err
|
||||
}
|
||||
|
||||
return newOpError("wayland", errors.Join(hangupErr, removeErr), true)
|
||||
}
|
||||
|
||||
func (w *waylandOp) Is(o Op) bool {
|
||||
target, ok := o.(*waylandOp)
|
||||
return ok && w != nil && target != nil &&
|
||||
w.dst.Is(target.dst) && w.src.Is(target.src) &&
|
||||
w.appID == target.appID && w.instanceID == target.instanceID
|
||||
}
|
||||
|
||||
func (w *waylandOp) Path() string { return w.dst.String() }
|
||||
func (w *waylandOp) String() string { return fmt.Sprintf("wayland socket at %q", w.dst) }
|
||||
@ -1,157 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/internal/acl"
|
||||
)
|
||||
|
||||
func TestWaylandOp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkOpBehaviour(t, []opBehaviourTestCase{
|
||||
{"chmod", 0xbeef, 0xff, &waylandOp{nil,
|
||||
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
|
||||
m("/run/user/1971/wayland-0"),
|
||||
"org.chromium.Chromium",
|
||||
"ebf083d1b175911782d413369b64ce7c",
|
||||
}, []stub.Call{
|
||||
call("waylandNew", stub.ExpectArgs{m("/run/user/1971/wayland-0"), m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"}, nil, nil),
|
||||
call("verbosef", stub.ExpectArgs{"wayland pathname socket on %q via %q", []any{m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/1971/wayland-0")}}, nil, nil),
|
||||
call("chmod", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", os.FileMode(0)}, nil, stub.UniqueError(3)),
|
||||
}, &OpError{Op: "wayland", Err: errors.Join(stub.UniqueError(3), os.ErrInvalid)}, nil, nil},
|
||||
|
||||
{"aclUpdate", 0xbeef, 0xff, &waylandOp{nil,
|
||||
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
|
||||
m("/run/user/1971/wayland-0"),
|
||||
"org.chromium.Chromium",
|
||||
"ebf083d1b175911782d413369b64ce7c",
|
||||
}, []stub.Call{
|
||||
call("waylandNew", stub.ExpectArgs{m("/run/user/1971/wayland-0"), m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"}, nil, nil),
|
||||
call("verbosef", stub.ExpectArgs{"wayland pathname socket on %q via %q", []any{m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/1971/wayland-0")}}, nil, nil),
|
||||
call("chmod", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", os.FileMode(0)}, nil, nil),
|
||||
call("aclUpdate", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", 0xbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, stub.UniqueError(2)),
|
||||
}, &OpError{Op: "wayland", Err: errors.Join(stub.UniqueError(2), os.ErrInvalid)}, nil, nil},
|
||||
|
||||
{"remove", 0xbeef, 0xff, &waylandOp{nil,
|
||||
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
|
||||
m("/run/user/1971/wayland-0"),
|
||||
"org.chromium.Chromium",
|
||||
"ebf083d1b175911782d413369b64ce7c",
|
||||
}, []stub.Call{
|
||||
call("waylandNew", stub.ExpectArgs{m("/run/user/1971/wayland-0"), m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"}, nil, nil),
|
||||
call("verbosef", stub.ExpectArgs{"wayland pathname socket on %q via %q", []any{m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/1971/wayland-0")}}, nil, nil),
|
||||
call("chmod", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", os.FileMode(0)}, nil, nil),
|
||||
call("aclUpdate", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", 0xbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, nil),
|
||||
}, nil, []stub.Call{
|
||||
call("verbosef", stub.ExpectArgs{"hanging up wayland socket on %q", []any{m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland")}}, nil, nil),
|
||||
call("remove", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"}, nil, stub.UniqueError(1)),
|
||||
}, &OpError{Op: "wayland", Err: errors.Join(stub.UniqueError(1)), Revert: true}},
|
||||
|
||||
{"success", 0xbeef, 0xff, &waylandOp{nil,
|
||||
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
|
||||
m("/run/user/1971/wayland-0"),
|
||||
"org.chromium.Chromium",
|
||||
"ebf083d1b175911782d413369b64ce7c",
|
||||
}, []stub.Call{
|
||||
call("waylandNew", stub.ExpectArgs{m("/run/user/1971/wayland-0"), m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"}, nil, nil),
|
||||
call("verbosef", stub.ExpectArgs{"wayland pathname socket on %q via %q", []any{m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/1971/wayland-0")}}, nil, nil),
|
||||
call("chmod", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", os.FileMode(0)}, nil, nil),
|
||||
call("aclUpdate", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", 0xbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, nil),
|
||||
}, nil, []stub.Call{
|
||||
call("verbosef", stub.ExpectArgs{"hanging up wayland socket on %q", []any{m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland")}}, nil, nil),
|
||||
call("remove", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"}, nil, nil),
|
||||
}, nil},
|
||||
})
|
||||
|
||||
checkOpsBuilder(t, "Wayland", []opsBuilderTestCase{
|
||||
{"chromium", 0xcafe, func(_ *testing.T, sys *I) {
|
||||
sys.Wayland(
|
||||
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
|
||||
m("/run/user/1971/wayland-0"),
|
||||
"org.chromium.Chromium",
|
||||
"ebf083d1b175911782d413369b64ce7c",
|
||||
)
|
||||
}, []Op{&waylandOp{nil,
|
||||
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
|
||||
m("/run/user/1971/wayland-0"),
|
||||
"org.chromium.Chromium",
|
||||
"ebf083d1b175911782d413369b64ce7c",
|
||||
}}, stub.Expect{}},
|
||||
})
|
||||
|
||||
checkOpIs(t, []opIsTestCase{
|
||||
{"dst differs", &waylandOp{nil,
|
||||
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7d/wayland"),
|
||||
m("/run/user/1971/wayland-0"),
|
||||
"org.chromium.Chromium",
|
||||
"ebf083d1b175911782d413369b64ce7c",
|
||||
}, &waylandOp{nil,
|
||||
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
|
||||
m("/run/user/1971/wayland-0"),
|
||||
"org.chromium.Chromium",
|
||||
"ebf083d1b175911782d413369b64ce7c",
|
||||
}, false},
|
||||
|
||||
{"src differs", &waylandOp{nil,
|
||||
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
|
||||
m("/run/user/1971/wayland-1"),
|
||||
"org.chromium.Chromium",
|
||||
"ebf083d1b175911782d413369b64ce7c",
|
||||
}, &waylandOp{nil,
|
||||
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
|
||||
m("/run/user/1971/wayland-0"),
|
||||
"org.chromium.Chromium",
|
||||
"ebf083d1b175911782d413369b64ce7c",
|
||||
}, false},
|
||||
|
||||
{"appID differs", &waylandOp{nil,
|
||||
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
|
||||
m("/run/user/1971/wayland-0"),
|
||||
"org.chromium",
|
||||
"ebf083d1b175911782d413369b64ce7c",
|
||||
}, &waylandOp{nil,
|
||||
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
|
||||
m("/run/user/1971/wayland-0"),
|
||||
"org.chromium.Chromium",
|
||||
"ebf083d1b175911782d413369b64ce7c",
|
||||
}, false},
|
||||
|
||||
{"instanceID differs", &waylandOp{nil,
|
||||
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
|
||||
m("/run/user/1971/wayland-0"),
|
||||
"org.chromium.Chromium",
|
||||
"ebf083d1b175911782d413369b64ce7d",
|
||||
}, &waylandOp{nil,
|
||||
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
|
||||
m("/run/user/1971/wayland-0"),
|
||||
"org.chromium.Chromium",
|
||||
"ebf083d1b175911782d413369b64ce7c",
|
||||
}, false},
|
||||
|
||||
{"equals", &waylandOp{nil,
|
||||
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
|
||||
m("/run/user/1971/wayland-0"),
|
||||
"org.chromium.Chromium",
|
||||
"ebf083d1b175911782d413369b64ce7c",
|
||||
}, &waylandOp{nil,
|
||||
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
|
||||
m("/run/user/1971/wayland-0"),
|
||||
"org.chromium.Chromium",
|
||||
"ebf083d1b175911782d413369b64ce7c",
|
||||
}, true},
|
||||
})
|
||||
|
||||
checkOpMeta(t, []opMetaTestCase{
|
||||
{"chromium", &waylandOp{nil,
|
||||
m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
|
||||
m("/run/user/1971/wayland-0"),
|
||||
"org.chromium.Chromium",
|
||||
"ebf083d1b175911782d413369b64ce7c",
|
||||
}, Process, "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
|
||||
`wayland socket at "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"`},
|
||||
})
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package info
|
||||
package internal
|
||||
|
||||
// FallbackVersion is returned when a version string was not set by the linker.
|
||||
const FallbackVersion = "dirty"
|
||||
@ -1,94 +0,0 @@
|
||||
package wayland
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/container/check"
|
||||
)
|
||||
|
||||
// SecurityContext holds resources associated with a Wayland security_context.
|
||||
type SecurityContext struct {
|
||||
// Pipe with its write end passed to security-context-v1.
|
||||
closeFds [2]int
|
||||
}
|
||||
|
||||
// Close releases any resources held by [SecurityContext], and prevents further
|
||||
// connections to its associated socket.
|
||||
func (sc *SecurityContext) Close() error {
|
||||
if sc == nil {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
return errors.Join(
|
||||
syscall.Close(sc.closeFds[1]),
|
||||
syscall.Close(sc.closeFds[0]),
|
||||
)
|
||||
}
|
||||
|
||||
// New creates a new security context on the Wayland display at displayPath
|
||||
// and associates it with a new socket bound to bindPath.
|
||||
//
|
||||
// New does not attach a finalizer to the resulting [SecurityContext] struct.
|
||||
// The caller is responsible for calling [SecurityContext.Close].
|
||||
//
|
||||
// A non-nil error unwraps to concrete type [Error].
|
||||
func New(displayPath, bindPath *check.Absolute, appID, instanceID string) (*SecurityContext, error) {
|
||||
// ensure bindPath is available
|
||||
if f, err := os.Create(bindPath.String()); err != nil {
|
||||
return nil, &Error{RCreate, bindPath.String(), displayPath.String(), err}
|
||||
} else if err = f.Close(); err != nil {
|
||||
return nil, &Error{RCreate, bindPath.String(), displayPath.String(), err}
|
||||
} else if err = os.Remove(bindPath.String()); err != nil {
|
||||
return nil, &Error{RCreate, bindPath.String(), displayPath.String(), err}
|
||||
}
|
||||
|
||||
if fd, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0); err != nil {
|
||||
return nil, &Error{RHostSocket, bindPath.String(), displayPath.String(), err}
|
||||
} else if err = syscall.Connect(fd, &syscall.SockaddrUnix{Name: displayPath.String()}); err != nil {
|
||||
_ = syscall.Close(fd)
|
||||
return nil, &Error{RHostConnect, bindPath.String(), displayPath.String(), err}
|
||||
} else {
|
||||
closeFds, bindErr := securityContextBindPipe(fd, bindPath, appID, instanceID)
|
||||
if bindErr != nil {
|
||||
// do not leak the pipe and socket
|
||||
err = errors.Join(bindErr, // already wrapped
|
||||
syscall.Close(closeFds[1]),
|
||||
syscall.Close(closeFds[0]),
|
||||
syscall.Close(fd),
|
||||
)
|
||||
}
|
||||
return &SecurityContext{closeFds}, err
|
||||
}
|
||||
}
|
||||
|
||||
// securityContextBindPipe binds a socket associated to a security context created on serverFd,
|
||||
// returning the pipe file descriptors used for security-context-v1 close_fd.
|
||||
//
|
||||
// A non-nil error unwraps to concrete type [Error].
|
||||
func securityContextBindPipe(
|
||||
serverFd int,
|
||||
bindPath *check.Absolute,
|
||||
appID, instanceID string,
|
||||
) ([2]int, error) {
|
||||
// write end passed to security-context-v1 close_fd
|
||||
var closeFds [2]int
|
||||
if err := syscall.Pipe2(closeFds[0:], syscall.O_CLOEXEC); err != nil {
|
||||
return closeFds, err
|
||||
}
|
||||
|
||||
// returned error is already wrapped
|
||||
if err := securityContextBind(
|
||||
bindPath.String(),
|
||||
serverFd,
|
||||
appID, instanceID,
|
||||
closeFds[1],
|
||||
); err != nil {
|
||||
return closeFds, errors.Join(err,
|
||||
syscall.Close(closeFds[1]),
|
||||
syscall.Close(closeFds[0]),
|
||||
)
|
||||
} else {
|
||||
return closeFds, nil
|
||||
}
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
package wayland
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container/check"
|
||||
)
|
||||
|
||||
func TestSecurityContextClose(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if err := (*SecurityContext)(nil).Close(); !reflect.DeepEqual(err, os.ErrInvalid) {
|
||||
t.Fatalf("Close: error = %v", err)
|
||||
}
|
||||
|
||||
var ctx SecurityContext
|
||||
if err := syscall.Pipe2(ctx.closeFds[0:], syscall.O_CLOEXEC); err != nil {
|
||||
t.Fatalf("Pipe: error = %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = syscall.Close(ctx.closeFds[0]); _ = syscall.Close(ctx.closeFds[1]) })
|
||||
|
||||
if err := ctx.Close(); err != nil {
|
||||
t.Fatalf("Close: error = %v", err)
|
||||
}
|
||||
|
||||
wantErr := errors.Join(syscall.EBADF, syscall.EBADF)
|
||||
if err := ctx.Close(); !reflect.DeepEqual(err, wantErr) {
|
||||
t.Fatalf("Close: error = %#v, want %#v", err, wantErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewEnsure(t *testing.T) {
|
||||
existingDirPath := check.MustAbs(t.TempDir()).Append("dir")
|
||||
if err := os.MkdirAll(existingDirPath.String(), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
nonexistent := check.MustAbs("/proc/nonexistent")
|
||||
|
||||
wantErr := &Error{RCreate, existingDirPath.String(), nonexistent.String(), &os.PathError{
|
||||
Op: "open",
|
||||
Path: existingDirPath.String(),
|
||||
Err: syscall.EISDIR,
|
||||
}}
|
||||
if _, err := New(
|
||||
nonexistent,
|
||||
existingDirPath, "", "",
|
||||
); !reflect.DeepEqual(err, wantErr) {
|
||||
t.Fatalf("New: error = %#v, want %#v", err, wantErr)
|
||||
}
|
||||
}
|
||||
@ -1,117 +0,0 @@
|
||||
#include "wayland-client-helper.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "security-context-v1-protocol.h"
|
||||
#include <wayland-client.h>
|
||||
|
||||
static void registry_handle_global(
|
||||
void *data,
|
||||
struct wl_registry *registry,
|
||||
uint32_t name,
|
||||
const char *interface,
|
||||
uint32_t version) {
|
||||
struct wp_security_context_manager_v1 **out = data;
|
||||
|
||||
if (strcmp(interface, wp_security_context_manager_v1_interface.name) == 0)
|
||||
*out = wl_registry_bind(registry, name, &wp_security_context_manager_v1_interface, 1);
|
||||
}
|
||||
|
||||
static void registry_handle_global_remove(
|
||||
void *data,
|
||||
struct wl_registry *registry,
|
||||
uint32_t name) {} /* no-op */
|
||||
|
||||
static const struct wl_registry_listener registry_listener = {
|
||||
.global = registry_handle_global,
|
||||
.global_remove = registry_handle_global_remove,
|
||||
};
|
||||
|
||||
hakurei_wayland_res hakurei_security_context_bind(
|
||||
char *socket_path,
|
||||
int server_fd,
|
||||
const char *app_id,
|
||||
const char *instance_id,
|
||||
int close_fd) {
|
||||
hakurei_wayland_res res = HAKUREI_WAYLAND_SUCCESS; /* see wayland.go for handling */
|
||||
|
||||
struct wl_display *display = NULL;
|
||||
struct wl_registry *registry;
|
||||
struct wp_security_context_manager_v1 *security_context_manager = NULL;
|
||||
int event_cnt;
|
||||
int listen_fd = -1;
|
||||
struct sockaddr_un sockaddr = {0};
|
||||
struct wp_security_context_v1 *security_context;
|
||||
|
||||
display = wl_display_connect_to_fd(server_fd);
|
||||
if (display == NULL) {
|
||||
res = HAKUREI_WAYLAND_CONNECT;
|
||||
goto out;
|
||||
};
|
||||
|
||||
registry = wl_display_get_registry(display);
|
||||
if (wl_registry_add_listener(registry, ®istry_listener, &security_context_manager) < 0) {
|
||||
res = HAKUREI_WAYLAND_LISTENER;
|
||||
goto out;
|
||||
}
|
||||
event_cnt = wl_display_roundtrip(display);
|
||||
wl_registry_destroy(registry);
|
||||
if (event_cnt < 0) {
|
||||
res = HAKUREI_WAYLAND_ROUNDTRIP;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (security_context_manager == NULL) {
|
||||
res = HAKUREI_WAYLAND_NOT_AVAIL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (listen_fd < 0) {
|
||||
res = HAKUREI_WAYLAND_SOCKET;
|
||||
goto out;
|
||||
}
|
||||
|
||||
sockaddr.sun_family = AF_UNIX;
|
||||
snprintf(sockaddr.sun_path, sizeof(sockaddr.sun_path), "%s", socket_path);
|
||||
if (bind(listen_fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) != 0) {
|
||||
res = HAKUREI_WAYLAND_BIND;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (listen(listen_fd, 0) != 0) {
|
||||
res = HAKUREI_WAYLAND_LISTEN;
|
||||
goto out;
|
||||
}
|
||||
|
||||
security_context = wp_security_context_manager_v1_create_listener(security_context_manager, listen_fd, close_fd);
|
||||
if (security_context == NULL) { /* not reached */
|
||||
res = HAKUREI_WAYLAND_NOT_AVAIL;
|
||||
goto out;
|
||||
}
|
||||
wp_security_context_v1_set_sandbox_engine(security_context, "app.hakurei");
|
||||
wp_security_context_v1_set_app_id(security_context, app_id);
|
||||
wp_security_context_v1_set_instance_id(security_context, instance_id);
|
||||
wp_security_context_v1_commit(security_context);
|
||||
wp_security_context_v1_destroy(security_context);
|
||||
if (wl_display_roundtrip(display) < 0) {
|
||||
res = HAKUREI_WAYLAND_ROUNDTRIP;
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
if (listen_fd >= 0)
|
||||
close(listen_fd);
|
||||
if (security_context_manager != NULL)
|
||||
wp_security_context_manager_v1_destroy(security_context_manager);
|
||||
if (display != NULL)
|
||||
wl_display_disconnect(display);
|
||||
|
||||
free((void *)socket_path);
|
||||
free((void *)app_id);
|
||||
free((void *)instance_id);
|
||||
return res;
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
#include <stdbool.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
typedef enum {
|
||||
HAKUREI_WAYLAND_SUCCESS,
|
||||
/* wl_display_connect_to_fd failed, errno */
|
||||
HAKUREI_WAYLAND_CONNECT,
|
||||
/* wl_registry_add_listener failed, errno */
|
||||
HAKUREI_WAYLAND_LISTENER,
|
||||
/* wl_display_roundtrip failed, errno */
|
||||
HAKUREI_WAYLAND_ROUNDTRIP,
|
||||
/* compositor does not implement wp_security_context_v1 */
|
||||
HAKUREI_WAYLAND_NOT_AVAIL,
|
||||
/* socket failed, errno */
|
||||
HAKUREI_WAYLAND_SOCKET,
|
||||
/* bind failed, errno */
|
||||
HAKUREI_WAYLAND_BIND,
|
||||
/* listen failed, errno */
|
||||
HAKUREI_WAYLAND_LISTEN,
|
||||
|
||||
/* ensure pathname failed, implemented in conn.go */
|
||||
HAKUREI_WAYLAND_CREAT,
|
||||
/* socket for host server failed, implemented in conn.go */
|
||||
HAKUREI_WAYLAND_HOST_SOCKET,
|
||||
/* connect for host server failed, implemented in conn.go */
|
||||
HAKUREI_WAYLAND_HOST_CONNECT,
|
||||
} hakurei_wayland_res;
|
||||
|
||||
hakurei_wayland_res hakurei_security_context_bind(
|
||||
char *socket_path,
|
||||
int server_fd,
|
||||
const char *app_id,
|
||||
const char *instance_id,
|
||||
int close_fd);
|
||||
|
||||
/* returns whether the specified size fits in the sun_path field of sockaddr_un */
|
||||
static inline bool hakurei_is_valid_size_sun_path(size_t sz) {
|
||||
struct sockaddr_un sockaddr;
|
||||
return sz <= sizeof(sockaddr.sun_path);
|
||||
};
|
||||
@ -1,161 +0,0 @@
|
||||
// Package wayland implements Wayland security_context_v1 protocol.
|
||||
package wayland
|
||||
|
||||
//go:generate sh -c "wayland-scanner client-header `pkg-config --variable=datarootdir wayland-protocols`/wayland-protocols/staging/security-context/security-context-v1.xml security-context-v1-protocol.h"
|
||||
//go:generate sh -c "wayland-scanner private-code `pkg-config --variable=datarootdir wayland-protocols`/wayland-protocols/staging/security-context/security-context-v1.xml security-context-v1-protocol.c"
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: --static wayland-client
|
||||
#cgo freebsd openbsd LDFLAGS: -lwayland-client
|
||||
|
||||
#include "wayland-client-helper.h"
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// Display contains the name of the server socket
|
||||
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1147)
|
||||
// which is concatenated with XDG_RUNTIME_DIR
|
||||
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1171)
|
||||
// or used as-is if absolute
|
||||
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1176).
|
||||
Display = "WAYLAND_DISPLAY"
|
||||
|
||||
// FallbackName is used as the wayland socket name if WAYLAND_DISPLAY is unset
|
||||
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1149).
|
||||
FallbackName = "wayland-0"
|
||||
)
|
||||
|
||||
type (
|
||||
// Res is the outcome of a call to [New].
|
||||
Res = C.hakurei_wayland_res
|
||||
|
||||
// An Error represents a failure during [New].
|
||||
Error struct {
|
||||
// Where the failure occurred.
|
||||
Cause Res
|
||||
// Attempted pathname socket.
|
||||
Path string
|
||||
// Pathname socket to host server. Omitted for libwayland errors.
|
||||
Host string
|
||||
// Global errno value set during the fault.
|
||||
Errno error
|
||||
}
|
||||
)
|
||||
|
||||
// withPrefix returns prefix suffixed with errno description if available.
|
||||
func (e *Error) withPrefix(prefix string) string {
|
||||
if e.Errno == nil {
|
||||
return prefix
|
||||
}
|
||||
return prefix + ": " + e.Errno.Error()
|
||||
}
|
||||
|
||||
const (
|
||||
// RSuccess is returned on a successful call.
|
||||
RSuccess Res = C.HAKUREI_WAYLAND_SUCCESS
|
||||
// RConnect is returned if wl_display_connect_to_fd failed. The global errno is set.
|
||||
RConnect Res = C.HAKUREI_WAYLAND_CONNECT
|
||||
// RListener is returned if wl_registry_add_listener failed. The global errno is set.
|
||||
RListener Res = C.HAKUREI_WAYLAND_LISTENER
|
||||
// RRoundtrip is returned if wl_display_roundtrip failed. The global errno is set.
|
||||
RRoundtrip Res = C.HAKUREI_WAYLAND_ROUNDTRIP
|
||||
// RNotAvail is returned if compositor does not implement wp_security_context_v1.
|
||||
RNotAvail Res = C.HAKUREI_WAYLAND_NOT_AVAIL
|
||||
// RSocket is returned if socket failed. The global errno is set.
|
||||
RSocket Res = C.HAKUREI_WAYLAND_SOCKET
|
||||
// RBind is returned if bind failed. The global errno is set.
|
||||
RBind Res = C.HAKUREI_WAYLAND_BIND
|
||||
// RListen is returned if listen failed. The global errno is set.
|
||||
RListen Res = C.HAKUREI_WAYLAND_LISTEN
|
||||
|
||||
// RCreate is returned if ensuring pathname availability failed. Returned by [New].
|
||||
RCreate Res = C.HAKUREI_WAYLAND_CREAT
|
||||
// RHostSocket is returned if socket failed for host server. Returned by [New].
|
||||
RHostSocket Res = C.HAKUREI_WAYLAND_HOST_SOCKET
|
||||
// RHostConnect is returned if connect failed for host server. Returned by [New].
|
||||
RHostConnect Res = C.HAKUREI_WAYLAND_HOST_CONNECT
|
||||
)
|
||||
|
||||
func (e *Error) Unwrap() error { return e.Errno }
|
||||
func (e *Error) Message() string { return e.Error() }
|
||||
func (e *Error) Error() string {
|
||||
switch e.Cause {
|
||||
case RSuccess:
|
||||
if e.Errno == nil {
|
||||
return "success"
|
||||
}
|
||||
return e.Errno.Error()
|
||||
|
||||
case RConnect:
|
||||
return e.withPrefix("wl_display_connect_to_fd failed")
|
||||
case RListener:
|
||||
return e.withPrefix("wl_registry_add_listener failed")
|
||||
case RRoundtrip:
|
||||
return e.withPrefix("wl_display_roundtrip failed")
|
||||
case RNotAvail:
|
||||
return "compositor does not implement security_context_v1"
|
||||
|
||||
case RSocket:
|
||||
if e.Errno == nil {
|
||||
return "socket operation failed"
|
||||
}
|
||||
return "socket: " + e.Errno.Error()
|
||||
case RBind:
|
||||
return e.withPrefix("cannot bind " + e.Path)
|
||||
case RListen:
|
||||
return e.withPrefix("cannot listen on " + e.Path)
|
||||
|
||||
case RCreate:
|
||||
if e.Errno == nil {
|
||||
return "cannot ensure wayland pathname socket"
|
||||
}
|
||||
return e.Errno.Error()
|
||||
case RHostSocket:
|
||||
return e.withPrefix("socket")
|
||||
case RHostConnect:
|
||||
return e.withPrefix("cannot connect to " + e.Host)
|
||||
|
||||
default:
|
||||
return e.withPrefix("impossible outcome") /* not reached */
|
||||
}
|
||||
}
|
||||
|
||||
// securityContextBind calls hakurei_security_context_bind.
|
||||
//
|
||||
// A non-nil error has concrete type [Error].
|
||||
func securityContextBind(
|
||||
socketPath string,
|
||||
serverFd int,
|
||||
appID, instanceID string,
|
||||
closeFd int,
|
||||
) error {
|
||||
if hasNull(socketPath) || hasNull(appID) || hasNull(instanceID) {
|
||||
return &Error{Cause: RBind, Path: socketPath, Errno: errors.New("argument contains NUL character")}
|
||||
}
|
||||
if !C.hakurei_is_valid_size_sun_path(C.size_t(len(socketPath))) {
|
||||
return &Error{Cause: RBind, Path: socketPath, Errno: errors.New("socket pathname too long")}
|
||||
}
|
||||
|
||||
var e Error
|
||||
e.Cause, e.Errno = C.hakurei_security_context_bind(
|
||||
C.CString(socketPath),
|
||||
C.int(serverFd),
|
||||
C.CString(appID),
|
||||
C.CString(instanceID),
|
||||
C.int(closeFd),
|
||||
)
|
||||
|
||||
if e.Cause == RSuccess {
|
||||
return nil
|
||||
}
|
||||
e.Path = socketPath
|
||||
return &e
|
||||
}
|
||||
|
||||
// hasNull returns whether s contains the NUL character.
|
||||
func hasNull(s string) bool { return strings.IndexByte(s, 0) > -1 }
|
||||
@ -1,137 +0,0 @@
|
||||
package wayland
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container/stub"
|
||||
)
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
err Error
|
||||
want string
|
||||
}{
|
||||
{"success", Error{
|
||||
Cause: RSuccess,
|
||||
}, "success"},
|
||||
|
||||
{"success errno", Error{
|
||||
Cause: RSuccess,
|
||||
Errno: stub.UniqueError(0),
|
||||
}, "unique error 0 injected by the test suite"},
|
||||
|
||||
{"wl_display_connect_to_fd", Error{
|
||||
Cause: RConnect,
|
||||
Errno: stub.UniqueError(1),
|
||||
}, "wl_display_connect_to_fd failed: unique error 1 injected by the test suite"},
|
||||
|
||||
{"wl_registry_add_listener", Error{
|
||||
Cause: RListener,
|
||||
Errno: stub.UniqueError(2),
|
||||
}, "wl_registry_add_listener failed: unique error 2 injected by the test suite"},
|
||||
|
||||
{"wl_display_roundtrip", Error{
|
||||
Cause: RRoundtrip,
|
||||
Errno: stub.UniqueError(3),
|
||||
}, "wl_display_roundtrip failed: unique error 3 injected by the test suite"},
|
||||
|
||||
{"not available", Error{
|
||||
Cause: RNotAvail,
|
||||
}, "compositor does not implement security_context_v1"},
|
||||
|
||||
{"not available errno", Error{
|
||||
Cause: RNotAvail,
|
||||
Errno: syscall.EAGAIN,
|
||||
}, "compositor does not implement security_context_v1"},
|
||||
|
||||
{"socket", Error{
|
||||
Cause: RSocket,
|
||||
Errno: stub.UniqueError(4),
|
||||
}, "socket: unique error 4 injected by the test suite"},
|
||||
|
||||
{"bind", Error{
|
||||
Cause: RBind,
|
||||
Path: "/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland",
|
||||
Errno: stub.UniqueError(5),
|
||||
}, "cannot bind /hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland: unique error 5 injected by the test suite"},
|
||||
|
||||
{"listen", Error{
|
||||
Cause: RListen,
|
||||
Path: "/hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland",
|
||||
Errno: stub.UniqueError(6),
|
||||
}, "cannot listen on /hakurei.0/18783d07791f2460dbbcffb76c24c9e6/wayland: unique error 6 injected by the test suite"},
|
||||
|
||||
{"socket invalid", Error{
|
||||
Cause: RSocket,
|
||||
}, "socket operation failed"},
|
||||
|
||||
{"create", Error{
|
||||
Cause: RCreate,
|
||||
}, "cannot ensure wayland pathname socket"},
|
||||
|
||||
{"create path", Error{
|
||||
Cause: RCreate,
|
||||
Errno: &os.PathError{Op: "create", Path: "/proc/nonexistent", Err: syscall.EEXIST},
|
||||
}, "create /proc/nonexistent: file exists"},
|
||||
|
||||
{"host socket", Error{
|
||||
Cause: RHostSocket,
|
||||
Errno: stub.UniqueError(7),
|
||||
}, "socket: unique error 7 injected by the test suite"},
|
||||
|
||||
{"host connect", Error{
|
||||
Cause: RHostConnect,
|
||||
Host: "/run/user/1971/wayland-1",
|
||||
Errno: stub.UniqueError(8),
|
||||
}, "cannot connect to /run/user/1971/wayland-1: unique error 8 injected by the test suite"},
|
||||
|
||||
{"invalid", Error{
|
||||
Cause: 0xbad,
|
||||
}, "impossible outcome"},
|
||||
|
||||
{"invalid errno", Error{
|
||||
Cause: 0xbad,
|
||||
Errno: stub.UniqueError(9),
|
||||
}, "impossible outcome: unique error 9 injected by the test suite"},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got := tc.err.Message(); got != tc.want {
|
||||
t.Errorf("Message: %q, want %q", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecurityContextBindValidate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("NUL", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
want := &Error{Cause: RBind, Path: "\x00", Errno: errors.New("argument contains NUL character")}
|
||||
if got := securityContextBind("\x00", -1, "\x00", "\x00", -1); !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("securityContextBind: error = %#v, want %#v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("long", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// 256 bytes
|
||||
const oversizedPath = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
|
||||
want := &Error{Cause: RBind, Path: oversizedPath, Errno: errors.New("socket pathname too long")}
|
||||
if got := securityContextBind(oversizedPath, -1, "", "", -1); !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("securityContextBind: error = %#v, want %#v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
27
ldd/error.go
Normal file
27
ldd/error.go
Normal file
@ -0,0 +1,27 @@
|
||||
package ldd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnexpectedSeparator = errors.New("unexpected separator")
|
||||
ErrPathNotAbsolute = errors.New("path not absolute")
|
||||
ErrBadLocationFormat = errors.New("bad location format")
|
||||
ErrUnexpectedNewline = errors.New("unexpected newline")
|
||||
)
|
||||
|
||||
type EntryUnexpectedSegmentsError string
|
||||
|
||||
func (e EntryUnexpectedSegmentsError) Is(err error) bool {
|
||||
var eq EntryUnexpectedSegmentsError
|
||||
if !errors.As(err, &eq) {
|
||||
return false
|
||||
}
|
||||
return e == eq
|
||||
}
|
||||
|
||||
func (e EntryUnexpectedSegmentsError) Error() string {
|
||||
return fmt.Sprintf("unexpected segments in entry %q", string(e))
|
||||
}
|
||||
68
ldd/exec.go
68
ldd/exec.go
@ -3,7 +3,6 @@ package ldd
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
@ -17,26 +16,17 @@ import (
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
const (
|
||||
// msgStaticSuffix is the suffix of message printed to stderr by musl on a statically linked program.
|
||||
msgStaticSuffix = ": Not a valid dynamic program"
|
||||
// msgStaticGlibc is a substring of the message printed to stderr by glibc on a statically linked program.
|
||||
msgStaticGlibc = "not a dynamic executable"
|
||||
|
||||
// lddName is the file name of ldd(1) passed to exec.LookPath.
|
||||
lddName = "ldd"
|
||||
// lddTimeout is the maximum duration ldd(1) is allowed to ran for before it is terminated.
|
||||
lddTimeout = 4 * time.Second
|
||||
var (
|
||||
msgStatic = []byte("Not a valid dynamic program")
|
||||
msgStaticGlibc = []byte("not a dynamic executable")
|
||||
)
|
||||
|
||||
// Resolve runs ldd(1) in a strict sandbox and connects its stdout to a [Decoder].
|
||||
//
|
||||
// The returned error has concrete type
|
||||
// [exec.Error] or [check.AbsoluteError] for fault during lookup of ldd(1),
|
||||
// [os.SyscallError] for fault creating the stdout pipe,
|
||||
// [container.StartError] for fault during either stage of container setup.
|
||||
// Otherwise, it passes through the return values of [Decoder.Decode].
|
||||
func Resolve(ctx context.Context, msg message.Msg, pathname *check.Absolute) ([]*Entry, error) {
|
||||
func Exec(ctx context.Context, msg message.Msg, p string) ([]*Entry, error) {
|
||||
const (
|
||||
lddName = "ldd"
|
||||
lddTimeout = 4 * time.Second
|
||||
)
|
||||
|
||||
c, cancel := context.WithTimeout(ctx, lddTimeout)
|
||||
defer cancel()
|
||||
|
||||
@ -47,24 +37,18 @@ func Resolve(ctx context.Context, msg message.Msg, pathname *check.Absolute) ([]
|
||||
return nil, err
|
||||
}
|
||||
|
||||
z := container.NewCommand(c, msg, toolPath, lddName, pathname.String())
|
||||
z := container.NewCommand(c, msg, toolPath, lddName, p)
|
||||
z.Hostname = "hakurei-" + lddName
|
||||
z.SeccompFlags |= seccomp.AllowMultiarch
|
||||
z.SeccompPresets |= std.PresetStrict
|
||||
stderr := new(bytes.Buffer)
|
||||
stdout, stderr := new(bytes.Buffer), new(bytes.Buffer)
|
||||
z.Stdout = stdout
|
||||
z.Stderr = stderr
|
||||
z.
|
||||
Bind(fhs.AbsRoot, fhs.AbsRoot, 0).
|
||||
Proc(fhs.AbsProc).
|
||||
Dev(fhs.AbsDev, false)
|
||||
|
||||
var d *Decoder
|
||||
if r, err := z.StdoutPipe(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
d = NewDecoder(r)
|
||||
}
|
||||
|
||||
if err := z.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -72,35 +56,15 @@ func Resolve(ctx context.Context, msg message.Msg, pathname *check.Absolute) ([]
|
||||
if err := z.Serve(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entries, decodeErr := d.Decode()
|
||||
if decodeErr != nil {
|
||||
// do not cancel on successful decode to avoid racing with ldd(1) termination
|
||||
cancel()
|
||||
}
|
||||
|
||||
if err := z.Wait(); err != nil {
|
||||
m := stderr.Bytes()
|
||||
if bytes.Contains(m, []byte(msgStaticSuffix)) || bytes.Contains(m, []byte(msgStaticGlibc)) {
|
||||
if bytes.Contains(m, append([]byte(p+": "), msgStatic...)) ||
|
||||
bytes.Contains(m, msgStaticGlibc) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if decodeErr != nil {
|
||||
return nil, errors.Join(decodeErr, err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return entries, decodeErr
|
||||
}
|
||||
|
||||
// Exec runs ldd(1) in a restrictive [container] and connects it to a [Decoder], returning resulting entries.
|
||||
//
|
||||
// Deprecated: this function takes an unchecked pathname string.
|
||||
// Relative pathnames do not work in the container as working directory information is not sent.
|
||||
func Exec(ctx context.Context, msg message.Msg, pathname string) ([]*Entry, error) {
|
||||
if a, err := check.NewAbs(pathname); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return Resolve(ctx, msg, a)
|
||||
}
|
||||
v := stdout.Bytes()
|
||||
return Parse(v)
|
||||
}
|
||||
|
||||
@ -1,46 +0,0 @@
|
||||
package ldd_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/ldd"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
func TestExec(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("failure", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := ldd.Resolve(t.Context(), nil, check.MustAbs("/proc/nonexistent"))
|
||||
|
||||
var exitError *exec.ExitError
|
||||
if !errors.As(err, &exitError) {
|
||||
t.Fatalf("Exec: error has incorrect concrete type: %#v", err)
|
||||
}
|
||||
|
||||
const want = 1
|
||||
if got := exitError.ExitCode(); got != want {
|
||||
t.Fatalf("Exec: ExitCode = %d, want %d", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
msg := message.New(nil)
|
||||
msg.GetLogger().SetPrefix("check: ")
|
||||
if entries, err := ldd.Resolve(t.Context(), nil, check.MustAbs(container.MustExecutable(msg))); err != nil {
|
||||
t.Fatalf("Exec: error = %v", err)
|
||||
} else if testing.Verbose() {
|
||||
// result cannot be measured here as build information is not known
|
||||
t.Logf("Exec: %q", entries)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) { container.TryArgv0(nil); os.Exit(m.Run()) }
|
||||
245
ldd/ldd.go
245
ldd/ldd.go
@ -1,241 +1,66 @@
|
||||
// Package ldd provides a robust parser for ldd(1) output, and a convenience function
|
||||
// for running ldd(1) in a strict sandbox.
|
||||
//
|
||||
// Note: despite the additional hardening, great care must be taken when using ldd(1).
|
||||
// As a general rule, you must never run ldd(1) against a file that you do not wish to
|
||||
// execute within the same context.
|
||||
// Package ldd retrieves linker information by invoking ldd from glibc or musl and parsing its output.
|
||||
package ldd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"hakurei.app/container/check"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUnexpectedNewline is returned when encountering an unexpected empty line.
|
||||
ErrUnexpectedNewline = errors.New("unexpected newline")
|
||||
// ErrUnexpectedSeparator is returned when encountering an unexpected separator segment.
|
||||
ErrUnexpectedSeparator = errors.New("unexpected separator")
|
||||
// ErrBadLocationFormat is returned for an incorrectly formatted [Entry.Location] segment.
|
||||
ErrBadLocationFormat = errors.New("bad location format")
|
||||
)
|
||||
|
||||
// EntryUnexpectedSegmentsError is returned when encountering
|
||||
// a line containing unexpected number of segments.
|
||||
type EntryUnexpectedSegmentsError string
|
||||
|
||||
func (e EntryUnexpectedSegmentsError) Error() string {
|
||||
return fmt.Sprintf("unexpected segments in entry %q", string(e))
|
||||
}
|
||||
|
||||
// An Entry represents one line of ldd(1) output.
|
||||
type Entry struct {
|
||||
// File name of required object.
|
||||
Name string `json:"name"`
|
||||
// Absolute pathname of matched object. Only populated for the long variant.
|
||||
Path *check.Absolute `json:"path,omitempty"`
|
||||
// Address at which the object is loaded.
|
||||
Name string `json:"name,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Location uint64 `json:"location"`
|
||||
}
|
||||
|
||||
const (
|
||||
// entrySegmentIndexName is the index of the segment holding [Entry.Name].
|
||||
entrySegmentIndexName = 0
|
||||
// entrySegmentIndexPath is the index of the segment holding [Entry.Path],
|
||||
// present only for a line describing a fully populated [Entry].
|
||||
entrySegmentIndexPath = 2
|
||||
// entrySegmentIndexSeparator is the index of the segment containing the magic bytes entrySegmentFullSeparator,
|
||||
// present only for a line describing a fully populated [Entry].
|
||||
entrySegmentIndexSeparator = 1
|
||||
// entrySegmentIndexLocation is the index of the segment holding [Entry.Location]
|
||||
// for a line describing a fully populated [Entry].
|
||||
entrySegmentIndexLocation = 3
|
||||
// entrySegmentIndexLocationShort is the index of the segment holding [Entry.Location]
|
||||
// for a line describing only [Entry.Name].
|
||||
entrySegmentIndexLocationShort = 1
|
||||
func Parse(p []byte) ([]*Entry, error) {
|
||||
payload := strings.Split(strings.TrimSpace(string(p)), "\n")
|
||||
result := make([]*Entry, len(payload))
|
||||
|
||||
// entrySegmentSep is the byte separating segments in an [Entry] line.
|
||||
entrySegmentSep = ' '
|
||||
// entrySegmentFullSeparator is the exact contents of the segment at index entrySegmentIndexSeparator.
|
||||
entrySegmentFullSeparator = "=>"
|
||||
|
||||
// entrySegmentLocationLengthMin is the minimum possible length of a segment corresponding to [Entry.Location].
|
||||
entrySegmentLocationLengthMin = 4
|
||||
// entrySegmentLocationPrefix are magic bytes prefixing a segment corresponding to [Entry.Location].
|
||||
entrySegmentLocationPrefix = "(0x"
|
||||
// entrySegmentLocationSuffix is the magic byte suffixing a segment corresponding to [Entry.Location].
|
||||
entrySegmentLocationSuffix = ')'
|
||||
)
|
||||
|
||||
// decodeLocationSegment decodes and saves the segment corresponding to [Entry.Location].
|
||||
func (e *Entry) decodeLocationSegment(segment []byte) (err error) {
|
||||
if len(segment) < entrySegmentLocationLengthMin ||
|
||||
segment[len(segment)-1] != entrySegmentLocationSuffix ||
|
||||
string(segment[:len(entrySegmentLocationPrefix)]) != entrySegmentLocationPrefix {
|
||||
return ErrBadLocationFormat
|
||||
for i, ent := range payload {
|
||||
if len(ent) == 0 {
|
||||
return nil, ErrUnexpectedNewline
|
||||
}
|
||||
|
||||
e.Location, err = strconv.ParseUint(string(segment[3:len(segment)-1]), 16, 64)
|
||||
return
|
||||
}
|
||||
segment := strings.SplitN(ent, " ", 5)
|
||||
|
||||
// UnmarshalText parses a line of ldd(1) output and saves it to [Entry].
|
||||
func (e *Entry) UnmarshalText(data []byte) error {
|
||||
var (
|
||||
segments = bytes.SplitN(data, []byte{entrySegmentSep}, 5)
|
||||
// segment to pass to decodeLocationSegment
|
||||
iL int
|
||||
)
|
||||
// location index
|
||||
var iL int
|
||||
|
||||
switch len(segments) {
|
||||
switch len(segment) {
|
||||
case 2: // /lib/ld-musl-x86_64.so.1 (0x7f04d14ef000)
|
||||
iL = entrySegmentIndexLocationShort
|
||||
e.Name = string(bytes.TrimSpace(segments[entrySegmentIndexName]))
|
||||
|
||||
iL = 1
|
||||
result[i] = &Entry{Name: strings.TrimSpace(segment[0])}
|
||||
case 4: // libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7f04d14ef000)
|
||||
iL = entrySegmentIndexLocation
|
||||
if string(segments[entrySegmentIndexSeparator]) != entrySegmentFullSeparator {
|
||||
return ErrUnexpectedSeparator
|
||||
iL = 3
|
||||
if segment[1] != "=>" {
|
||||
return nil, ErrUnexpectedSeparator
|
||||
}
|
||||
if a, err := check.NewAbs(string(segments[entrySegmentIndexPath])); err != nil {
|
||||
return err
|
||||
} else {
|
||||
e.Path = a
|
||||
if !path.IsAbs(segment[2]) {
|
||||
return nil, ErrPathNotAbsolute
|
||||
}
|
||||
result[i] = &Entry{
|
||||
Name: strings.TrimSpace(segment[0]),
|
||||
Path: segment[2],
|
||||
}
|
||||
e.Name = string(bytes.TrimSpace(segments[entrySegmentIndexName]))
|
||||
|
||||
default:
|
||||
return EntryUnexpectedSegmentsError(data)
|
||||
return nil, EntryUnexpectedSegmentsError(ent)
|
||||
}
|
||||
|
||||
return e.decodeLocationSegment(segments[iL])
|
||||
}
|
||||
|
||||
// String returns the musl ldd(1) representation of [Entry].
|
||||
func (e *Entry) String() string {
|
||||
// nameInvalid is used for a zero-length e.Name
|
||||
const nameInvalid = "invalid"
|
||||
|
||||
var buf strings.Builder
|
||||
|
||||
// libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
|
||||
l := len(e.Name) + 1
|
||||
if e.Name == "" {
|
||||
l += len(nameInvalid)
|
||||
}
|
||||
if e.Path != nil {
|
||||
l += len(entrySegmentFullSeparator) + 1 + len(e.Path.String()) + 1
|
||||
}
|
||||
l += len(entrySegmentLocationPrefix) + 1<<4 + 1
|
||||
buf.Grow(l)
|
||||
|
||||
if e.Name != "" {
|
||||
buf.WriteString(e.Name + " ")
|
||||
if loc, err := parseLocation(segment[iL]); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
buf.WriteString(nameInvalid + " ")
|
||||
result[i].Location = loc
|
||||
}
|
||||
if e.Path != nil {
|
||||
buf.WriteString(entrySegmentFullSeparator + " " + e.Path.String() + " ")
|
||||
}
|
||||
buf.WriteString(entrySegmentLocationPrefix + strconv.FormatUint(e.Location, 16) + string(entrySegmentLocationSuffix))
|
||||
|
||||
return buf.String()
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Path returns a deduplicated slice of absolute directory paths in entries.
|
||||
func Path(entries []*Entry) []*check.Absolute {
|
||||
p := make([]*check.Absolute, 0, len(entries)*2)
|
||||
for _, entry := range entries {
|
||||
if entry.Path != nil {
|
||||
p = append(p, entry.Path.Dir())
|
||||
func parseLocation(s string) (uint64, error) {
|
||||
if len(s) < 4 || s[len(s)-1] != ')' || s[:3] != "(0x" {
|
||||
return math.MaxUint64, ErrBadLocationFormat
|
||||
}
|
||||
if a, err := check.NewAbs(entry.Name); err == nil {
|
||||
p = append(p, a.Dir())
|
||||
}
|
||||
}
|
||||
check.SortAbs(p)
|
||||
return check.CompactAbs(p)
|
||||
return strconv.ParseUint(s[3:len(s)-1], 16, 64)
|
||||
}
|
||||
|
||||
// A Decoder reads and decodes [Entry] values from an input stream.
|
||||
//
|
||||
// The zero value is not safe for use.
|
||||
type Decoder struct {
|
||||
s *bufio.Scanner
|
||||
|
||||
// Whether the current line is not the first line.
|
||||
notFirst bool
|
||||
// Whether s has no more tokens.
|
||||
depleted bool
|
||||
// Holds onto the first error encountered while parsing.
|
||||
err error
|
||||
}
|
||||
|
||||
// NewDecoder returns a new decoder that reads from r.
|
||||
//
|
||||
// The decoder introduces its own buffering and may read
|
||||
// data from r beyond the [Entry] values requested.
|
||||
func NewDecoder(r io.Reader) *Decoder { return &Decoder{s: bufio.NewScanner(r)} }
|
||||
|
||||
// Scan advances the [Decoder] to the next [Entry] and
|
||||
// stores the result in the value pointed to by v.
|
||||
func (d *Decoder) Scan(v *Entry) bool {
|
||||
if d.s == nil || d.err != nil || d.depleted {
|
||||
return false
|
||||
}
|
||||
if !d.s.Scan() {
|
||||
d.depleted = true
|
||||
return false
|
||||
}
|
||||
|
||||
data := d.s.Bytes()
|
||||
if len(data) == 0 {
|
||||
if d.notFirst {
|
||||
if d.s.Scan() && d.err == nil {
|
||||
d.err = ErrUnexpectedNewline
|
||||
}
|
||||
// trailing newline is allowed (glibc)
|
||||
return false
|
||||
}
|
||||
|
||||
// leading newline is allowed (musl)
|
||||
d.notFirst = true
|
||||
return d.Scan(v)
|
||||
}
|
||||
|
||||
d.notFirst = true
|
||||
d.err = v.UnmarshalText(data)
|
||||
return d.err == nil
|
||||
}
|
||||
|
||||
// Err returns the first non-EOF error that was encountered
|
||||
// by the underlying [bufio.Scanner] or [Entry].
|
||||
func (d *Decoder) Err() error {
|
||||
if d.err != nil || d.s == nil {
|
||||
return d.err
|
||||
}
|
||||
return d.s.Err()
|
||||
}
|
||||
|
||||
// Decode reads from the input stream until there are no more entries
|
||||
// and returns the results in a slice.
|
||||
func (d *Decoder) Decode() ([]*Entry, error) {
|
||||
var entries []*Entry
|
||||
|
||||
e := new(Entry)
|
||||
for d.Scan(e) {
|
||||
entries = append(entries, e)
|
||||
e = new(Entry)
|
||||
}
|
||||
return entries, d.Err()
|
||||
}
|
||||
|
||||
// Parse returns a slice of addresses to [Entry] decoded from p.
|
||||
func Parse(p []byte) ([]*Entry, error) { return NewDecoder(bytes.NewReader(p)).Decode() }
|
||||
|
||||
204
ldd/ldd_test.go
204
ldd/ldd_test.go
@ -1,23 +1,14 @@
|
||||
package ldd_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/ldd"
|
||||
)
|
||||
|
||||
func TestEntryUnexpectedSegmentsError(t *testing.T) {
|
||||
const want = `unexpected segments in entry "\x00"`
|
||||
if got := ldd.EntryUnexpectedSegmentsError("\x00").Error(); got != want {
|
||||
t.Fatalf("Error: %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeError(t *testing.T) {
|
||||
func TestParseError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
@ -29,36 +20,24 @@ func TestDecodeError(t *testing.T) {
|
||||
|
||||
libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
|
||||
`, ldd.ErrUnexpectedNewline},
|
||||
|
||||
{"unexpected separator", `
|
||||
libzstd.so.1 = /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
|
||||
`, ldd.ErrUnexpectedSeparator},
|
||||
|
||||
{"path not absolute", `
|
||||
libzstd.so.1 => usr/lib/libzstd.so.1 (0x7ff71bfd2000)
|
||||
`, &check.AbsoluteError{Pathname: "usr/lib/libzstd.so.1"}},
|
||||
|
||||
`, ldd.ErrPathNotAbsolute},
|
||||
{"unexpected segments", `
|
||||
meow libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
|
||||
`, ldd.EntryUnexpectedSegmentsError("meow libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)")},
|
||||
|
||||
{"bad location format", `
|
||||
libzstd.so.1 => /usr/lib/libzstd.so.1 7ff71bfd2000
|
||||
`, ldd.ErrBadLocationFormat},
|
||||
|
||||
{"valid", ``, nil},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
d := ldd.NewDecoder(strings.NewReader(tc.out))
|
||||
|
||||
if _, err := d.Decode(); !reflect.DeepEqual(err, tc.wantErr) {
|
||||
t.Errorf("Decode: error = %v, wantErr %v", err, tc.wantErr)
|
||||
}
|
||||
if d.Scan(new(ldd.Entry)) {
|
||||
t.Fatalf("Scan: unexpected true")
|
||||
if _, err := ldd.Parse([]byte(tc.out)); !errors.Is(err, tc.wantErr) {
|
||||
t.Errorf("Parse() error = %v, wantErr %v", err, tc.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -70,7 +49,6 @@ func TestParse(t *testing.T) {
|
||||
testCases := []struct {
|
||||
file, out string
|
||||
want []*ldd.Entry
|
||||
paths []*check.Absolute
|
||||
}{
|
||||
{"musl /bin/kmod", `
|
||||
/lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)
|
||||
@ -78,38 +56,30 @@ libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
|
||||
liblzma.so.5 => /usr/lib/liblzma.so.5 (0x7ff71bf9a000)
|
||||
libz.so.1 => /lib/libz.so.1 (0x7ff71bf80000)
|
||||
libcrypto.so.3 => /lib/libcrypto.so.3 (0x7ff71ba00000)
|
||||
libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`, []*ldd.Entry{
|
||||
{"/lib/ld-musl-x86_64.so.1", nil, 0x7ff71c0a4000},
|
||||
{"libzstd.so.1", check.MustAbs("/usr/lib/libzstd.so.1"), 0x7ff71bfd2000},
|
||||
{"liblzma.so.5", check.MustAbs("/usr/lib/liblzma.so.5"), 0x7ff71bf9a000},
|
||||
{"libz.so.1", check.MustAbs("/lib/libz.so.1"), 0x7ff71bf80000},
|
||||
{"libcrypto.so.3", check.MustAbs("/lib/libcrypto.so.3"), 0x7ff71ba00000},
|
||||
{"libc.musl-x86_64.so.1", check.MustAbs("/lib/ld-musl-x86_64.so.1"), 0x7ff71c0a4000},
|
||||
}, []*check.Absolute{
|
||||
check.MustAbs("/lib"),
|
||||
check.MustAbs("/usr/lib"),
|
||||
libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`,
|
||||
[]*ldd.Entry{
|
||||
{"/lib/ld-musl-x86_64.so.1", "", 0x7ff71c0a4000},
|
||||
{"libzstd.so.1", "/usr/lib/libzstd.so.1", 0x7ff71bfd2000},
|
||||
{"liblzma.so.5", "/usr/lib/liblzma.so.5", 0x7ff71bf9a000},
|
||||
{"libz.so.1", "/lib/libz.so.1", 0x7ff71bf80000},
|
||||
{"libcrypto.so.3", "/lib/libcrypto.so.3", 0x7ff71ba00000},
|
||||
{"libc.musl-x86_64.so.1", "/lib/ld-musl-x86_64.so.1", 0x7ff71c0a4000},
|
||||
}},
|
||||
|
||||
{"glibc /nix/store/rc3n2r3nffpib2gqpxlkjx36frw6n34z-kmod-31/bin/kmod", `
|
||||
linux-vdso.so.1 (0x00007ffed65be000)
|
||||
libzstd.so.1 => /nix/store/80pxmvb9q43kh9rkjagc4h41vf6dh1y6-zstd-1.5.6/lib/libzstd.so.1 (0x00007f3199cd1000)
|
||||
liblzma.so.5 => /nix/store/g78jna1i5qhh8gqs4mr64648f0szqgw4-xz-5.4.7/lib/liblzma.so.5 (0x00007f3199ca2000)
|
||||
libc.so.6 => /nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libc.so.6 (0x00007f3199ab5000)
|
||||
libpthread.so.0 => /nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libpthread.so.0 (0x00007f3199ab0000)
|
||||
/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/ld-linux-x86-64.so.2 => /nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib64/ld-linux-x86-64.so.2 (0x00007f3199da5000)`, []*ldd.Entry{
|
||||
{"linux-vdso.so.1", nil, 0x00007ffed65be000},
|
||||
{"libzstd.so.1", check.MustAbs("/nix/store/80pxmvb9q43kh9rkjagc4h41vf6dh1y6-zstd-1.5.6/lib/libzstd.so.1"), 0x00007f3199cd1000},
|
||||
{"liblzma.so.5", check.MustAbs("/nix/store/g78jna1i5qhh8gqs4mr64648f0szqgw4-xz-5.4.7/lib/liblzma.so.5"), 0x00007f3199ca2000},
|
||||
{"libc.so.6", check.MustAbs("/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libc.so.6"), 0x00007f3199ab5000},
|
||||
{"libpthread.so.0", check.MustAbs("/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libpthread.so.0"), 0x00007f3199ab0000},
|
||||
{"/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/ld-linux-x86-64.so.2", check.MustAbs("/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib64/ld-linux-x86-64.so.2"), 0x00007f3199da5000},
|
||||
}, []*check.Absolute{
|
||||
check.MustAbs("/nix/store/80pxmvb9q43kh9rkjagc4h41vf6dh1y6-zstd-1.5.6/lib"),
|
||||
check.MustAbs("/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib"),
|
||||
check.MustAbs("/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib64"),
|
||||
check.MustAbs("/nix/store/g78jna1i5qhh8gqs4mr64648f0szqgw4-xz-5.4.7/lib"),
|
||||
/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/ld-linux-x86-64.so.2 => /nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib64/ld-linux-x86-64.so.2 (0x00007f3199da5000)`,
|
||||
[]*ldd.Entry{
|
||||
{"linux-vdso.so.1", "", 0x00007ffed65be000},
|
||||
{"libzstd.so.1", "/nix/store/80pxmvb9q43kh9rkjagc4h41vf6dh1y6-zstd-1.5.6/lib/libzstd.so.1", 0x00007f3199cd1000},
|
||||
{"liblzma.so.5", "/nix/store/g78jna1i5qhh8gqs4mr64648f0szqgw4-xz-5.4.7/lib/liblzma.so.5", 0x00007f3199ca2000},
|
||||
{"libc.so.6", "/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libc.so.6", 0x00007f3199ab5000},
|
||||
{"libpthread.so.0", "/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libpthread.so.0", 0x00007f3199ab0000},
|
||||
{"/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/ld-linux-x86-64.so.2", "/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib64/ld-linux-x86-64.so.2", 0x00007f3199da5000},
|
||||
}},
|
||||
|
||||
{"glibc /usr/bin/xdg-dbus-proxy", `
|
||||
linux-vdso.so.1 (0x00007725f5772000)
|
||||
libglib-2.0.so.0 => /usr/lib/libglib-2.0.so.0 (0x00007725f55d5000)
|
||||
@ -123,129 +93,31 @@ libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`, []*ldd.Entr
|
||||
libmount.so.1 => /usr/lib/libmount.so.1 (0x00007725f5076000)
|
||||
libffi.so.8 => /usr/lib/libffi.so.8 (0x00007725f506b000)
|
||||
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007725f5774000)
|
||||
libblkid.so.1 => /usr/lib/libblkid.so.1 (0x00007725f5032000)`, []*ldd.Entry{
|
||||
{"linux-vdso.so.1", nil, 0x00007725f5772000},
|
||||
{"libglib-2.0.so.0", check.MustAbs("/usr/lib/libglib-2.0.so.0"), 0x00007725f55d5000},
|
||||
{"libgio-2.0.so.0", check.MustAbs("/usr/lib/libgio-2.0.so.0"), 0x00007725f5406000},
|
||||
{"libgobject-2.0.so.0", check.MustAbs("/usr/lib/libgobject-2.0.so.0"), 0x00007725f53a6000},
|
||||
{"libgcc_s.so.1", check.MustAbs("/usr/lib/libgcc_s.so.1"), 0x00007725f5378000},
|
||||
{"libc.so.6", check.MustAbs("/usr/lib/libc.so.6"), 0x00007725f5187000},
|
||||
{"libpcre2-8.so.0", check.MustAbs("/usr/lib/libpcre2-8.so.0"), 0x00007725f50e8000},
|
||||
{"libgmodule-2.0.so.0", check.MustAbs("/usr/lib/libgmodule-2.0.so.0"), 0x00007725f50df000},
|
||||
{"libz.so.1", check.MustAbs("/usr/lib/libz.so.1"), 0x00007725f50c6000},
|
||||
{"libmount.so.1", check.MustAbs("/usr/lib/libmount.so.1"), 0x00007725f5076000},
|
||||
{"libffi.so.8", check.MustAbs("/usr/lib/libffi.so.8"), 0x00007725f506b000},
|
||||
{"/lib64/ld-linux-x86-64.so.2", check.MustAbs("/usr/lib64/ld-linux-x86-64.so.2"), 0x00007725f5774000},
|
||||
{"libblkid.so.1", check.MustAbs("/usr/lib/libblkid.so.1"), 0x00007725f5032000},
|
||||
}, []*check.Absolute{
|
||||
check.MustAbs("/lib64"),
|
||||
check.MustAbs("/usr/lib"),
|
||||
check.MustAbs("/usr/lib64"),
|
||||
libblkid.so.1 => /usr/lib/libblkid.so.1 (0x00007725f5032000)`,
|
||||
[]*ldd.Entry{
|
||||
{"linux-vdso.so.1", "", 0x00007725f5772000},
|
||||
{"libglib-2.0.so.0", "/usr/lib/libglib-2.0.so.0", 0x00007725f55d5000},
|
||||
{"libgio-2.0.so.0", "/usr/lib/libgio-2.0.so.0", 0x00007725f5406000},
|
||||
{"libgobject-2.0.so.0", "/usr/lib/libgobject-2.0.so.0", 0x00007725f53a6000},
|
||||
{"libgcc_s.so.1", "/usr/lib/libgcc_s.so.1", 0x00007725f5378000},
|
||||
{"libc.so.6", "/usr/lib/libc.so.6", 0x00007725f5187000},
|
||||
{"libpcre2-8.so.0", "/usr/lib/libpcre2-8.so.0", 0x00007725f50e8000},
|
||||
{"libgmodule-2.0.so.0", "/usr/lib/libgmodule-2.0.so.0", 0x00007725f50df000},
|
||||
{"libz.so.1", "/usr/lib/libz.so.1", 0x00007725f50c6000},
|
||||
{"libmount.so.1", "/usr/lib/libmount.so.1", 0x00007725f5076000},
|
||||
{"libffi.so.8", "/usr/lib/libffi.so.8", 0x00007725f506b000},
|
||||
{"/lib64/ld-linux-x86-64.so.2", "/usr/lib64/ld-linux-x86-64.so.2", 0x00007725f5774000},
|
||||
{"libblkid.so.1", "/usr/lib/libblkid.so.1", 0x00007725f5032000},
|
||||
}},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.file, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got, err := ldd.Parse([]byte(tc.out)); err != nil {
|
||||
t.Errorf("Parse: error = %v", err)
|
||||
t.Errorf("Parse() error = %v", err)
|
||||
} else if !reflect.DeepEqual(got, tc.want) {
|
||||
t.Errorf("Parse: \n%s\nwant\n%s", mustMarshalJSON(got), mustMarshalJSON(tc.want))
|
||||
} else if paths := ldd.Path(got); !reflect.DeepEqual(paths, tc.paths) {
|
||||
t.Errorf("Paths: %v, want %v", paths, tc.paths)
|
||||
t.Errorf("Parse() got = %#v, want %#v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
e ldd.Entry
|
||||
want string
|
||||
}{
|
||||
{"ld", ldd.Entry{
|
||||
Name: "/lib/ld-musl-x86_64.so.1",
|
||||
Location: 0x7ff71c0a4000,
|
||||
}, `/lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`},
|
||||
|
||||
{"libzstd", ldd.Entry{
|
||||
Name: "libzstd.so.1",
|
||||
Path: check.MustAbs("/usr/lib/libzstd.so.1"),
|
||||
Location: 0x7ff71bfd2000,
|
||||
}, `libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)`},
|
||||
|
||||
{"liblzma", ldd.Entry{
|
||||
Name: "liblzma.so.5",
|
||||
Path: check.MustAbs("/usr/lib/liblzma.so.5"),
|
||||
Location: 0x7ff71bf9a000,
|
||||
}, `liblzma.so.5 => /usr/lib/liblzma.so.5 (0x7ff71bf9a000)`},
|
||||
|
||||
{"libz", ldd.Entry{
|
||||
Name: "libz.so.1",
|
||||
Path: check.MustAbs("/lib/libz.so.1"),
|
||||
Location: 0x7ff71bf80000,
|
||||
}, `libz.so.1 => /lib/libz.so.1 (0x7ff71bf80000)`},
|
||||
|
||||
{"libcrypto", ldd.Entry{
|
||||
Name: "libcrypto.so.3",
|
||||
Path: check.MustAbs("/lib/libcrypto.so.3"),
|
||||
Location: 0x7ff71ba00000,
|
||||
}, `libcrypto.so.3 => /lib/libcrypto.so.3 (0x7ff71ba00000)`},
|
||||
|
||||
{"libc", ldd.Entry{
|
||||
Name: "libc.musl-x86_64.so.1",
|
||||
Path: check.MustAbs("/lib/ld-musl-x86_64.so.1"),
|
||||
Location: 0x7ff71c0a4000,
|
||||
}, `libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`},
|
||||
|
||||
{"invalid", ldd.Entry{
|
||||
Location: 0x7ff71c0a4000,
|
||||
}, `invalid (0x7ff71c0a4000)`},
|
||||
|
||||
{"invalid long", ldd.Entry{
|
||||
Path: check.MustAbs("/lib/ld-musl-x86_64.so.1"),
|
||||
Location: 0x7ff71c0a4000,
|
||||
}, `invalid => /lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("decode", func(t *testing.T) {
|
||||
if tc.e.Name == "" {
|
||||
return
|
||||
}
|
||||
t.Parallel()
|
||||
|
||||
var got ldd.Entry
|
||||
if err := got.UnmarshalText([]byte(tc.want)); err != nil {
|
||||
t.Fatalf("UnmarshalText: error = %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(&got, &tc.e) {
|
||||
t.Errorf("UnmarshalText: %#v, want %#v", got, tc.e)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("encode", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got := tc.e.String(); got != tc.want {
|
||||
t.Errorf("String: %s, want %s", got, tc.want)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// mustMarshalJSON calls [json.Marshal] and returns the resulting data.
|
||||
func mustMarshalJSON(v any) []byte {
|
||||
if data, err := json.Marshal(v); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
20
ldd/path.go
Normal file
20
ldd/path.go
Normal file
@ -0,0 +1,20 @@
|
||||
package ldd
|
||||
|
||||
import (
|
||||
"hakurei.app/container/check"
|
||||
)
|
||||
|
||||
// Path returns a deterministic, deduplicated slice of absolute directory paths in entries.
|
||||
func Path(entries []*Entry) []*check.Absolute {
|
||||
p := make([]*check.Absolute, 0, len(entries)*2)
|
||||
for _, entry := range entries {
|
||||
if a, err := check.NewAbs(entry.Path); err == nil {
|
||||
p = append(p, a.Dir())
|
||||
}
|
||||
if a, err := check.NewAbs(entry.Name); err == nil {
|
||||
p = append(p, a.Dir())
|
||||
}
|
||||
}
|
||||
check.SortAbs(p)
|
||||
return check.CompactAbs(p)
|
||||
}
|
||||
17
package.nix
17
package.nix
@ -20,11 +20,10 @@
|
||||
|
||||
# for passthru.buildInputs
|
||||
go,
|
||||
clang,
|
||||
gcc,
|
||||
|
||||
# for check
|
||||
util-linux,
|
||||
nettools,
|
||||
|
||||
glibc, # for ldd
|
||||
withStatic ? stdenv.hostPlatform.isStatic,
|
||||
@ -66,7 +65,7 @@ buildGoModule rec {
|
||||
lib.attrsets.foldlAttrs
|
||||
(
|
||||
ldflags: name: value:
|
||||
ldflags ++ [ "-X hakurei.app/internal/info.${name}=${value}" ]
|
||||
ldflags ++ [ "-X hakurei.app/internal.${name}=${value}" ]
|
||||
)
|
||||
(
|
||||
[ "-s -w" ]
|
||||
@ -81,13 +80,8 @@ buildGoModule rec {
|
||||
hsuPath = "/run/wrappers/bin/hsu";
|
||||
};
|
||||
|
||||
env = {
|
||||
# use clang instead of gcc
|
||||
CC = "clang -O3 -Werror";
|
||||
|
||||
# nix build environment does not allow acls
|
||||
GO_TEST_SKIP_ACL = 1;
|
||||
};
|
||||
env.GO_TEST_SKIP_ACL = 1;
|
||||
|
||||
buildInputs = [
|
||||
libffi
|
||||
@ -104,9 +98,6 @@ buildGoModule rec {
|
||||
nativeBuildInputs = [
|
||||
pkg-config
|
||||
makeBinaryWrapper
|
||||
|
||||
# for container example
|
||||
nettools
|
||||
];
|
||||
|
||||
postInstall =
|
||||
@ -140,7 +131,7 @@ buildGoModule rec {
|
||||
|
||||
passthru.targetPkgs = [
|
||||
go
|
||||
clang
|
||||
gcc
|
||||
xorg.xorgproto
|
||||
util-linux
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/system/acl"
|
||||
)
|
||||
|
||||
// UpdatePerm calls UpdatePermType with the [Process] criteria.
|
||||
@ -13,7 +13,7 @@ import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/system/acl"
|
||||
)
|
||||
|
||||
const testFileName = "acl.test"
|
||||
@ -1,25 +0,0 @@
|
||||
// Package acl exposes the internal/acl package.
|
||||
//
|
||||
// Deprecated: This package will be removed in 0.4.
|
||||
package acl
|
||||
|
||||
import (
|
||||
_ "unsafe" // for go:linkname
|
||||
|
||||
"hakurei.app/internal/acl"
|
||||
)
|
||||
|
||||
type Perm = acl.Perm
|
||||
|
||||
const (
|
||||
Read = acl.Read
|
||||
Write = acl.Write
|
||||
Execute = acl.Execute
|
||||
)
|
||||
|
||||
// Update replaces ACL_USER entry with qualifier uid.
|
||||
//
|
||||
//go:linkname Update hakurei.app/internal/acl.Update
|
||||
func Update(name string, uid int, perms ...Perm) error
|
||||
|
||||
type Perms = acl.Perms
|
||||
90
system/acl/libacl-helper.c
Normal file
90
system/acl/libacl-helper.c
Normal file
@ -0,0 +1,90 @@
|
||||
#include "libacl-helper.h"
|
||||
#include <acl/libacl.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/acl.h>
|
||||
|
||||
int hakurei_acl_update_file_by_uid(const char *path_p, uid_t uid,
|
||||
acl_perm_t *perms, size_t plen) {
|
||||
int ret;
|
||||
bool v;
|
||||
int i;
|
||||
acl_t acl;
|
||||
acl_entry_t entry;
|
||||
acl_tag_t tag_type;
|
||||
void *qualifier_p;
|
||||
acl_permset_t permset;
|
||||
|
||||
ret = -1; /* acl_get_file */
|
||||
acl = acl_get_file(path_p, ACL_TYPE_ACCESS);
|
||||
if (acl == NULL)
|
||||
goto out;
|
||||
|
||||
/* prune entries by uid */
|
||||
for (i = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); i == 1;
|
||||
i = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry)) {
|
||||
ret = -2; /* acl_get_tag_type */
|
||||
if (acl_get_tag_type(entry, &tag_type) != 0)
|
||||
goto out;
|
||||
if (tag_type != ACL_USER)
|
||||
continue;
|
||||
|
||||
ret = -3; /* acl_get_qualifier */
|
||||
qualifier_p = acl_get_qualifier(entry);
|
||||
if (qualifier_p == NULL)
|
||||
goto out;
|
||||
v = *(uid_t *)qualifier_p == uid;
|
||||
acl_free(qualifier_p);
|
||||
|
||||
if (!v)
|
||||
continue;
|
||||
|
||||
ret = -4; /* acl_delete_entry */
|
||||
if (acl_delete_entry(acl, entry) != 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (plen == 0)
|
||||
goto set;
|
||||
|
||||
ret = -5; /* acl_create_entry */
|
||||
if (acl_create_entry(&acl, &entry) != 0)
|
||||
goto out;
|
||||
|
||||
ret = -6; /* acl_get_permset */
|
||||
if (acl_get_permset(entry, &permset) != 0)
|
||||
goto out;
|
||||
|
||||
ret = -7; /* acl_add_perm */
|
||||
for (i = 0; i < plen; i++) {
|
||||
if (acl_add_perm(permset, perms[i]) != 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = -8; /* acl_set_tag_type */
|
||||
if (acl_set_tag_type(entry, ACL_USER) != 0)
|
||||
goto out;
|
||||
|
||||
ret = -9; /* acl_set_qualifier */
|
||||
if (acl_set_qualifier(entry, (void *)&uid) != 0)
|
||||
goto out;
|
||||
|
||||
set:
|
||||
ret = -10; /* acl_calc_mask */
|
||||
if (acl_calc_mask(&acl) != 0)
|
||||
goto out;
|
||||
|
||||
ret = -11; /* acl_valid */
|
||||
if (acl_valid(acl) != 0)
|
||||
goto out;
|
||||
|
||||
ret = -12; /* acl_set_file */
|
||||
if (acl_set_file(path_p, ACL_TYPE_ACCESS, acl) == 0)
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
free((void *)path_p);
|
||||
if (acl != NULL)
|
||||
acl_free((void *)acl);
|
||||
return ret;
|
||||
}
|
||||
@ -3,7 +3,7 @@ package acl_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/system/acl"
|
||||
)
|
||||
|
||||
func TestPerms(t *testing.T) {
|
||||
@ -7,7 +7,7 @@ import (
|
||||
|
||||
"hakurei.app/container/stub"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/system/acl"
|
||||
)
|
||||
|
||||
func TestACLUpdateOp(t *testing.T) {
|
||||
@ -13,7 +13,7 @@ import (
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/dbus"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
// ErrDBusConfig is returned when a required [hst.BusConfig] argument is nil.
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/internal/dbus"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/dbus"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
func TestConfigArgs(t *testing.T) {
|
||||
@ -11,9 +11,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hakurei.app/internal/dbus"
|
||||
"hakurei.app/internal/helper"
|
||||
"hakurei.app/helper"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
func TestFinalise(t *testing.T) {
|
||||
@ -1,115 +0,0 @@
|
||||
// Package dbus exposes the internal/dbus package.
|
||||
//
|
||||
// Deprecated: This package will be removed in 0.4.
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
_ "unsafe" // for go:linkname
|
||||
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/dbus"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
type AddrEntry = dbus.AddrEntry
|
||||
|
||||
// EqualAddrEntries returns whether two slices of [AddrEntry] are equal.
|
||||
//
|
||||
//go:linkname EqualAddrEntries hakurei.app/internal/dbus.EqualAddrEntries
|
||||
func EqualAddrEntries(entries, target []AddrEntry) bool
|
||||
|
||||
// Parse parses D-Bus address according to
|
||||
// https://dbus.freedesktop.org/doc/dbus-specification.html#addresses
|
||||
//
|
||||
//go:linkname Parse hakurei.app/internal/dbus.Parse
|
||||
func Parse(addr []byte) ([]AddrEntry, error)
|
||||
|
||||
type ParseError = dbus.ParseError
|
||||
|
||||
const (
|
||||
ErrNoColon = dbus.ErrNoColon
|
||||
ErrBadPairSep = dbus.ErrBadPairSep
|
||||
ErrBadPairKey = dbus.ErrBadPairKey
|
||||
ErrBadPairVal = dbus.ErrBadPairVal
|
||||
ErrBadValLength = dbus.ErrBadValLength
|
||||
ErrBadValByte = dbus.ErrBadValByte
|
||||
ErrBadValHexLength = dbus.ErrBadValHexLength
|
||||
ErrBadValHexByte = dbus.ErrBadValHexByte
|
||||
)
|
||||
|
||||
type BadAddressError = dbus.BadAddressError
|
||||
|
||||
// ProxyPair is an upstream dbus address and a downstream socket path.
|
||||
type ProxyPair = dbus.ProxyPair
|
||||
|
||||
// Args returns the xdg-dbus-proxy arguments equivalent of [hst.BusConfig].
|
||||
//
|
||||
//go:linkname Args hakurei.app/internal/dbus.Args
|
||||
func Args(c *hst.BusConfig, bus ProxyPair) (args []string)
|
||||
|
||||
// NewConfig returns the address of a new [hst.BusConfig] with optional defaults.
|
||||
//
|
||||
//go:linkname NewConfig hakurei.app/internal/dbus.NewConfig
|
||||
func NewConfig(id string, defaults, mpris bool) *hst.BusConfig
|
||||
|
||||
const (
|
||||
/*
|
||||
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.
|
||||
|
||||
The address of the login session message bus is given in the DBUS_SESSION_BUS_ADDRESS environment variable.
|
||||
If DBUS_SESSION_BUS_ADDRESS is not set, or if it's set to the string "autolaunch:",
|
||||
the system should use platform-specific methods of locating a running D-Bus session server,
|
||||
or starting one if a running instance cannot be found.
|
||||
Note that this mechanism is not recommended for attempting to determine if a daemon is running.
|
||||
It is inherently racy to attempt to make this determination, since the bus daemon may be started just before or just after the determination is made.
|
||||
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.
|
||||
*/
|
||||
SessionBusAddress = dbus.SessionBusAddress
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
SystemBusAddress = dbus.SystemBusAddress
|
||||
|
||||
// FallbackSystemBusAddress is used when [SystemBusAddress] is not set.
|
||||
FallbackSystemBusAddress = dbus.FallbackSystemBusAddress
|
||||
)
|
||||
|
||||
// Address returns the session and system bus addresses copied from environment,
|
||||
// or appropriate fallback values if they are not set.
|
||||
//
|
||||
//go:linkname Address hakurei.app/internal/dbus.Address
|
||||
func Address() (session, system string)
|
||||
|
||||
// ProxyName is the file name or path to the proxy program.
|
||||
// Overriding ProxyName will only affect Proxy instance created after the change.
|
||||
//
|
||||
//go:linkname ProxyName hakurei.app/internal/dbus.ProxyName
|
||||
var ProxyName string
|
||||
|
||||
// Proxy holds the state of a xdg-dbus-proxy process, and should never be copied.
|
||||
type Proxy = dbus.Proxy
|
||||
|
||||
// Final describes the outcome of a proxy configuration.
|
||||
type Final = dbus.Final
|
||||
|
||||
// Finalise creates a checked argument writer for [Proxy].
|
||||
//
|
||||
//go:linkname Finalise hakurei.app/internal/dbus.Finalise
|
||||
func Finalise(sessionBus, systemBus ProxyPair, session, system *hst.BusConfig) (final *Final, err error)
|
||||
|
||||
// New returns a new instance of [Proxy].
|
||||
//
|
||||
//go:linkname New hakurei.app/internal/dbus.New
|
||||
func New(ctx context.Context, msg message.Msg, final *Final, output io.Writer) *Proxy
|
||||
@ -12,7 +12,7 @@ import (
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/container/std"
|
||||
"hakurei.app/internal/helper"
|
||||
"hakurei.app/helper"
|
||||
"hakurei.app/ldd"
|
||||
)
|
||||
|
||||
@ -54,7 +54,7 @@ func (p *Proxy) Start() error {
|
||||
}
|
||||
|
||||
var libPaths []*check.Absolute
|
||||
if entries, err := ldd.Resolve(ctx, p.msg, toolPath); err != nil {
|
||||
if entries, err := ldd.Exec(ctx, p.msg, toolPath.String()); err != nil {
|
||||
return err
|
||||
} else {
|
||||
libPaths = ldd.Path(entries)
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/internal/helper"
|
||||
"hakurei.app/helper"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) { container.TryArgv0(nil); helper.InternalHelperStub(); os.Exit(m.Run()) }
|
||||
@ -6,8 +6,8 @@ import (
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/helper"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/helper"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user