Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
cb513bb1cd
|
|||
|
f7bd28118c
|
|||
|
940ee00ffe
|
|||
|
b43d104680
|
|||
|
ddf48a6c22
|
|||
|
a0f499e30a
|
|||
|
d6b07f12ff
|
|||
|
65fe09caf9
|
|||
|
a1e5f020f4
|
|||
|
bd3fa53a55
|
|||
|
625632c593
|
|||
|
e71ae3b8c5
|
|||
|
9d7a19d162
|
|||
|
6ba19a7ba5
|
|||
|
749a2779f5
|
|||
|
e574042d76
|
|||
|
2b44493e8a
|
|||
|
c30dd4e630
|
|||
|
d90da1c8f5
|
|||
|
5853d7700f
|
|||
|
d5c7523726
|
|||
|
ddfcc51b91
|
|||
|
8ebedbd88a
|
|||
|
84e8142a2d
|
|||
|
2c7b7ad845
|
|||
|
72c2b66fc0
|
|||
|
356b42a406
|
|||
|
d9b6d48e7c
|
|||
|
087959e81b
|
@@ -8,7 +8,11 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://pkg.go.dev/hakurei.app"><img src="https://pkg.go.dev/badge/hakurei.app.svg" alt="Go Reference" /></a>
|
<a href="https://pkg.go.dev/hakurei.app"><img src="https://pkg.go.dev/badge/hakurei.app.svg" alt="Go Reference" /></a>
|
||||||
|
<a href="https://git.gensokyo.uk/security/hakurei/actions"><img src="https://git.gensokyo.uk/security/hakurei/actions/workflows/test.yml/badge.svg?branch=staging&style=flat-square" alt="Gitea Workflow Status" /></a>
|
||||||
|
<br/>
|
||||||
|
<a href="https://git.gensokyo.uk/security/hakurei/releases"><img src="https://img.shields.io/gitea/v/release/security/hakurei?gitea_url=https%3A%2F%2Fgit.gensokyo.uk&color=purple" alt="Release" /></a>
|
||||||
<a href="https://goreportcard.com/report/hakurei.app"><img src="https://goreportcard.com/badge/hakurei.app" alt="Go Report Card" /></a>
|
<a href="https://goreportcard.com/report/hakurei.app"><img src="https://goreportcard.com/badge/hakurei.app" alt="Go Report Card" /></a>
|
||||||
|
<a href="https://hakurei.app"><img src="https://img.shields.io/website?url=https%3A%2F%2Fhakurei.app" alt="Website" /></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Hakurei is a tool for running sandboxed graphical applications as dedicated subordinate users on the Linux kernel.
|
Hakurei is a tool for running sandboxed graphical applications as dedicated subordinate users on the Linux kernel.
|
||||||
|
|||||||
@@ -13,12 +13,11 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/cmd/hakurei/internal/app"
|
|
||||||
"hakurei.app/cmd/hakurei/internal/app/instance"
|
|
||||||
"hakurei.app/cmd/hakurei/internal/state"
|
|
||||||
"hakurei.app/command"
|
"hakurei.app/command"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal"
|
"hakurei.app/internal"
|
||||||
|
"hakurei.app/internal/app"
|
||||||
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/hlog"
|
||||||
"hakurei.app/system"
|
"hakurei.app/system"
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
@@ -33,7 +32,7 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
Flag(&flagVerbose, "v", command.BoolFlag(false), "Increase log verbosity").
|
Flag(&flagVerbose, "v", command.BoolFlag(false), "Increase log verbosity").
|
||||||
Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output in JSON when applicable")
|
Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output in JSON when applicable")
|
||||||
|
|
||||||
c.Command("shim", command.UsageInternal, func([]string) error { instance.ShimMain(); return errSuccess })
|
c.Command("shim", command.UsageInternal, func([]string) error { app.ShimMain(); return errSuccess })
|
||||||
|
|
||||||
c.Command("app", "Load app from configuration file", func(args []string) error {
|
c.Command("app", "Load app from configuration file", func(args []string) error {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
@@ -244,14 +243,14 @@ func runApp(config *hst.Config) {
|
|||||||
ctx, stop := signal.NotifyContext(context.Background(),
|
ctx, stop := signal.NotifyContext(context.Background(),
|
||||||
syscall.SIGINT, syscall.SIGTERM)
|
syscall.SIGINT, syscall.SIGTERM)
|
||||||
defer stop() // unreachable
|
defer stop() // unreachable
|
||||||
a := instance.MustNew(instance.ISetuid, ctx, std)
|
a := app.MustNew(ctx, std)
|
||||||
|
|
||||||
rs := new(app.RunState)
|
rs := new(app.RunState)
|
||||||
if sa, err := a.Seal(config); err != nil {
|
if sa, err := a.Seal(config); err != nil {
|
||||||
hlog.PrintBaseError(err, "cannot seal app:")
|
hlog.PrintBaseError(err, "cannot seal app:")
|
||||||
internal.Exit(1)
|
internal.Exit(1)
|
||||||
} else {
|
} else {
|
||||||
internal.Exit(instance.PrintRunStateErr(instance.ISetuid, rs, sa.Run(rs)))
|
internal.Exit(app.PrintRunStateErr(rs, sa.Run(rs)))
|
||||||
}
|
}
|
||||||
|
|
||||||
*(*int)(nil) = 0 // not reached
|
*(*int)(nil) = 0 // not reached
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
package instance
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"hakurei.app/cmd/hakurei/internal/app"
|
|
||||||
"hakurei.app/cmd/hakurei/internal/app/internal/setuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
func PrintRunStateErr(whence int, rs *app.RunState, runErr error) (code int) {
|
|
||||||
switch whence {
|
|
||||||
case ISetuid:
|
|
||||||
return setuid.PrintRunStateErr(rs, runErr)
|
|
||||||
default:
|
|
||||||
panic(syscall.EINVAL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
// Package instance exposes cross-package implementation details and provides constructors for builtin implementations.
|
|
||||||
package instance
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"hakurei.app/cmd/hakurei/internal/app"
|
|
||||||
"hakurei.app/cmd/hakurei/internal/app/internal/setuid"
|
|
||||||
"hakurei.app/internal/sys"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ISetuid = iota
|
|
||||||
)
|
|
||||||
|
|
||||||
func New(whence int, ctx context.Context, os sys.State) (app.App, error) {
|
|
||||||
switch whence {
|
|
||||||
case ISetuid:
|
|
||||||
return setuid.New(ctx, os)
|
|
||||||
default:
|
|
||||||
return nil, syscall.EINVAL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func MustNew(whence int, ctx context.Context, os sys.State) app.App {
|
|
||||||
a, err := New(whence, ctx, os)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("cannot create app: %v", err)
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package instance
|
|
||||||
|
|
||||||
import "hakurei.app/cmd/hakurei/internal/app/internal/setuid"
|
|
||||||
|
|
||||||
// ShimMain is the main function of the shim process and runs as the unconstrained target user.
|
|
||||||
func ShimMain() { setuid.ShimMain() }
|
|
||||||
@@ -10,8 +10,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"hakurei.app/cmd/hakurei/internal/state"
|
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/hlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import (
|
|||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/cmd/hakurei/internal/state"
|
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/hlog"
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,14 +5,13 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/cmd/hakurei/internal/app"
|
|
||||||
"hakurei.app/cmd/hakurei/internal/state"
|
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
testID = app.ID{
|
testID = state.ID{
|
||||||
0x8e, 0x2c, 0x76, 0xb0,
|
0x8e, 0x2c, 0x76, 0xb0,
|
||||||
0x66, 0xda, 0xbe, 0x57,
|
0x66, 0xda, 0xbe, 0x57,
|
||||||
0x4c, 0xf0, 0x73, 0xbd,
|
0x4c, 0xf0, 0x73, 0xbd,
|
||||||
@@ -257,8 +256,10 @@ App
|
|||||||
],
|
],
|
||||||
"container": {
|
"container": {
|
||||||
"hostname": "localhost",
|
"hostname": "localhost",
|
||||||
|
"wait_delay": -1,
|
||||||
"seccomp_flags": 1,
|
"seccomp_flags": 1,
|
||||||
"seccomp_presets": 1,
|
"seccomp_presets": 1,
|
||||||
|
"seccomp_compat": true,
|
||||||
"devel": true,
|
"devel": true,
|
||||||
"userns": true,
|
"userns": true,
|
||||||
"net": true,
|
"net": true,
|
||||||
@@ -383,8 +384,10 @@ App
|
|||||||
],
|
],
|
||||||
"container": {
|
"container": {
|
||||||
"hostname": "localhost",
|
"hostname": "localhost",
|
||||||
|
"wait_delay": -1,
|
||||||
"seccomp_flags": 1,
|
"seccomp_flags": 1,
|
||||||
"seccomp_presets": 1,
|
"seccomp_presets": 1,
|
||||||
|
"seccomp_compat": true,
|
||||||
"devel": true,
|
"devel": true,
|
||||||
"userns": true,
|
"userns": true,
|
||||||
"net": true,
|
"net": true,
|
||||||
@@ -460,7 +463,7 @@ func Test_printPs(t *testing.T) {
|
|||||||
{"no entries", make(state.Entries), false, false, " Instance PID Application Uptime\n"},
|
{"no entries", make(state.Entries), false, false, " Instance PID Application Uptime\n"},
|
||||||
{"no entries short", make(state.Entries), true, false, ""},
|
{"no entries short", make(state.Entries), true, false, ""},
|
||||||
{"nil instance", state.Entries{testID: nil}, false, false, " Instance PID Application Uptime\n"},
|
{"nil instance", state.Entries{testID: nil}, false, false, " Instance PID Application Uptime\n"},
|
||||||
{"state corruption", state.Entries{app.ID{}: testState}, false, false, " Instance PID Application Uptime\n"},
|
{"state corruption", state.Entries{state.ID{}: testState}, false, false, " Instance PID Application Uptime\n"},
|
||||||
|
|
||||||
{"valid pd", state.Entries{testID: &state.State{ID: testID, PID: 1 << 8, Config: new(hst.Config), Time: testAppTime}}, false, false, ` Instance PID Application Uptime
|
{"valid pd", state.Entries{testID: &state.State{ID: testID, PID: 1 << 8, Config: new(hst.Config), Time: testAppTime}}, false, false, ` Instance PID Application Uptime
|
||||||
8e2c76b0 256 0 (app.hakurei.8e2c76b0) 1h2m32s
|
8e2c76b0 256 0 (app.hakurei.8e2c76b0) 1h2m32s
|
||||||
@@ -563,8 +566,10 @@ func Test_printPs(t *testing.T) {
|
|||||||
],
|
],
|
||||||
"container": {
|
"container": {
|
||||||
"hostname": "localhost",
|
"hostname": "localhost",
|
||||||
|
"wait_delay": -1,
|
||||||
"seccomp_flags": 1,
|
"seccomp_flags": 1,
|
||||||
"seccomp_presets": 1,
|
"seccomp_presets": 1,
|
||||||
|
"seccomp_compat": true,
|
||||||
"devel": true,
|
"devel": true,
|
||||||
"userns": true,
|
"userns": true,
|
||||||
"net": true,
|
"net": true,
|
||||||
|
|||||||
@@ -215,15 +215,14 @@ stdenv.mkDerivation {
|
|||||||
# create binary cache
|
# create binary cache
|
||||||
closureInfo="${
|
closureInfo="${
|
||||||
closureInfo {
|
closureInfo {
|
||||||
rootPaths =
|
rootPaths = [
|
||||||
[
|
homeManagerConfiguration.activationPackage
|
||||||
homeManagerConfiguration.activationPackage
|
launcher
|
||||||
launcher
|
]
|
||||||
]
|
++ optionals gpu [
|
||||||
++ optionals gpu [
|
mesaWrappers
|
||||||
mesaWrappers
|
nixGL
|
||||||
nixGL
|
];
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
echo "copying application paths..."
|
echo "copying application paths..."
|
||||||
|
|||||||
45
container/capability.go
Normal file
45
container/capability.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package container
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_LINUX_CAPABILITY_VERSION_3 = 0x20080522
|
||||||
|
|
||||||
|
PR_CAP_AMBIENT = 0x2f
|
||||||
|
PR_CAP_AMBIENT_RAISE = 0x2
|
||||||
|
PR_CAP_AMBIENT_CLEAR_ALL = 0x4
|
||||||
|
|
||||||
|
CAP_SYS_ADMIN = 0x15
|
||||||
|
CAP_SETPCAP = 0x8
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
capHeader struct {
|
||||||
|
version uint32
|
||||||
|
pid int32
|
||||||
|
}
|
||||||
|
|
||||||
|
capData struct {
|
||||||
|
effective uint32
|
||||||
|
permitted uint32
|
||||||
|
inheritable uint32
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// See CAP_TO_INDEX in linux/capability.h:
|
||||||
|
func capToIndex(cap uintptr) uintptr { return cap >> 5 }
|
||||||
|
|
||||||
|
// See CAP_TO_MASK in linux/capability.h:
|
||||||
|
func capToMask(cap uintptr) uint32 { return 1 << uint(cap&31) }
|
||||||
|
|
||||||
|
func capset(hdrp *capHeader, datap *[2]capData) error {
|
||||||
|
if _, _, errno := syscall.Syscall(syscall.SYS_CAPSET,
|
||||||
|
uintptr(unsafe.Pointer(hdrp)),
|
||||||
|
uintptr(unsafe.Pointer(&datap[0])), 0); errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -17,6 +17,16 @@ import (
|
|||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Nonexistent is a path that cannot exist.
|
||||||
|
// /proc is chosen because a system with covered /proc is unsupported by this package.
|
||||||
|
Nonexistent = "/proc/nonexistent"
|
||||||
|
|
||||||
|
// CancelSignal is the signal expected by container init on context cancel.
|
||||||
|
// A custom [Container.Cancel] function must eventually deliver this signal.
|
||||||
|
CancelSignal = SIGTERM
|
||||||
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// Container represents a container environment being prepared or run.
|
// Container represents a container environment being prepared or run.
|
||||||
// None of [Container] methods are safe for concurrent use.
|
// None of [Container] methods are safe for concurrent use.
|
||||||
@@ -29,9 +39,6 @@ type (
|
|||||||
// with behaviour identical to its [exec.Cmd] counterpart.
|
// with behaviour identical to its [exec.Cmd] counterpart.
|
||||||
ExtraFiles []*os.File
|
ExtraFiles []*os.File
|
||||||
|
|
||||||
// Custom [exec.Cmd] initialisation function.
|
|
||||||
CommandContext func(ctx context.Context) (cmd *exec.Cmd)
|
|
||||||
|
|
||||||
// param encoder for shim and init
|
// param encoder for shim and init
|
||||||
setup *gob.Encoder
|
setup *gob.Encoder
|
||||||
// cancels cmd
|
// cancels cmd
|
||||||
@@ -59,6 +66,10 @@ type (
|
|||||||
Path string
|
Path string
|
||||||
// Initial process argv.
|
// Initial process argv.
|
||||||
Args []string
|
Args []string
|
||||||
|
// Deliver SIGINT to the initial process on context cancellation.
|
||||||
|
ForwardCancel bool
|
||||||
|
// time to wait for linger processes after death of initial process
|
||||||
|
AdoptWaitDelay time.Duration
|
||||||
|
|
||||||
// Mapped Uid in user namespace.
|
// Mapped Uid in user namespace.
|
||||||
Uid int
|
Uid int
|
||||||
@@ -68,6 +79,7 @@ type (
|
|||||||
Hostname string
|
Hostname string
|
||||||
// Sequential container setup ops.
|
// Sequential container setup ops.
|
||||||
*Ops
|
*Ops
|
||||||
|
|
||||||
// Seccomp system call filter rules.
|
// Seccomp system call filter rules.
|
||||||
SeccompRules []seccomp.NativeRule
|
SeccompRules []seccomp.NativeRule
|
||||||
// Extra seccomp flags.
|
// Extra seccomp flags.
|
||||||
@@ -76,6 +88,7 @@ type (
|
|||||||
SeccompPresets seccomp.FilterPreset
|
SeccompPresets seccomp.FilterPreset
|
||||||
// Do not load seccomp program.
|
// Do not load seccomp program.
|
||||||
SeccompDisable bool
|
SeccompDisable bool
|
||||||
|
|
||||||
// Permission bits of newly created parent directories.
|
// Permission bits of newly created parent directories.
|
||||||
// The zero value is interpreted as 0755.
|
// The zero value is interpreted as 0755.
|
||||||
ParentPerm os.FileMode
|
ParentPerm os.FileMode
|
||||||
@@ -88,12 +101,13 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Start starts the container init. The init process blocks until Serve is called.
|
||||||
func (p *Container) Start() error {
|
func (p *Container) Start() error {
|
||||||
if p.cmd != nil {
|
if p.cmd != nil {
|
||||||
return errors.New("sandbox: already started")
|
return errors.New("container: already started")
|
||||||
}
|
}
|
||||||
if p.Ops == nil || len(*p.Ops) == 0 {
|
if p.Ops == nil || len(*p.Ops) == 0 {
|
||||||
return errors.New("sandbox: starting an empty container")
|
return errors.New("container: starting an empty container")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(p.ctx)
|
ctx, cancel := context.WithCancel(p.ctx)
|
||||||
@@ -116,19 +130,22 @@ func (p *Container) Start() error {
|
|||||||
p.SeccompPresets |= seccomp.PresetDenyTTY
|
p.SeccompPresets |= seccomp.PresetDenyTTY
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.CommandContext != nil {
|
if p.AdoptWaitDelay == 0 {
|
||||||
p.cmd = p.CommandContext(ctx)
|
p.AdoptWaitDelay = 5 * time.Second
|
||||||
} else {
|
}
|
||||||
p.cmd = exec.CommandContext(ctx, MustExecutable())
|
// to allow disabling this behaviour
|
||||||
p.cmd.Args = []string{"init"}
|
if p.AdoptWaitDelay < 0 {
|
||||||
|
p.AdoptWaitDelay = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.cmd = exec.CommandContext(ctx, MustExecutable())
|
||||||
|
p.cmd.Args = []string{initName}
|
||||||
p.cmd.Stdin, p.cmd.Stdout, p.cmd.Stderr = p.Stdin, p.Stdout, p.Stderr
|
p.cmd.Stdin, p.cmd.Stdout, p.cmd.Stderr = p.Stdin, p.Stdout, p.Stderr
|
||||||
p.cmd.WaitDelay = p.WaitDelay
|
p.cmd.WaitDelay = p.WaitDelay
|
||||||
if p.Cancel != nil {
|
if p.Cancel != nil {
|
||||||
p.cmd.Cancel = func() error { return p.Cancel(p.cmd) }
|
p.cmd.Cancel = func() error { return p.Cancel(p.cmd) }
|
||||||
} else {
|
} else {
|
||||||
p.cmd.Cancel = func() error { return p.cmd.Process.Signal(SIGTERM) }
|
p.cmd.Cancel = func() error { return p.cmd.Process.Signal(CancelSignal) }
|
||||||
}
|
}
|
||||||
p.cmd.Dir = "/"
|
p.cmd.Dir = "/"
|
||||||
p.cmd.SysProcAttr = &SysProcAttr{
|
p.cmd.SysProcAttr = &SysProcAttr{
|
||||||
@@ -162,6 +179,8 @@ func (p *Container) Start() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Serve serves [Container.Params] to the container init.
|
||||||
|
// Serve must only be called once.
|
||||||
func (p *Container) Serve() error {
|
func (p *Container) Serve() error {
|
||||||
if p.setup == nil {
|
if p.setup == nil {
|
||||||
panic("invalid serve")
|
panic("invalid serve")
|
||||||
@@ -215,6 +234,7 @@ func (p *Container) Serve() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait waits for the container init process to exit.
|
||||||
func (p *Container) Wait() error { defer p.cancel(); return p.cmd.Wait() }
|
func (p *Container) Wait() error { defer p.cancel(); return p.cmd.Wait() }
|
||||||
|
|
||||||
func (p *Container) String() string {
|
func (p *Container) String() string {
|
||||||
@@ -222,6 +242,14 @@ func (p *Container) String() string {
|
|||||||
p.Args, !p.SeccompDisable, len(p.SeccompRules), int(p.SeccompFlags), int(p.SeccompPresets))
|
p.Args, !p.SeccompDisable, len(p.SeccompRules), int(p.SeccompFlags), int(p.SeccompPresets))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProcessState returns the address to os.ProcessState held by the underlying [exec.Cmd].
|
||||||
|
func (p *Container) ProcessState() *os.ProcessState {
|
||||||
|
if p.cmd == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return p.cmd.ProcessState
|
||||||
|
}
|
||||||
|
|
||||||
func New(ctx context.Context, name string, args ...string) *Container {
|
func New(ctx context.Context, name string, args ...string) *Container {
|
||||||
return &Container{name: name, ctx: ctx,
|
return &Container{name: name, ctx: ctx,
|
||||||
Params: Params{Args: append([]string{name}, args...), Dir: "/", Ops: new(Ops)},
|
Params: Params{Args: append([]string{name}, args...), Dir: "/", Ops: new(Ops)},
|
||||||
|
|||||||
@@ -4,28 +4,85 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
|
"hakurei.app/command"
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
"hakurei.app/container/vfs"
|
"hakurei.app/container/vfs"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal"
|
"hakurei.app/internal"
|
||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/hlog"
|
||||||
"hakurei.app/ldd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ignore = "\x00"
|
ignore = "\x00"
|
||||||
ignoreV = -1
|
ignoreV = -1
|
||||||
|
|
||||||
|
pathWantMnt = "/etc/hakurei/want-mnt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var containerTestCases = []struct {
|
||||||
|
name string
|
||||||
|
filter bool
|
||||||
|
session bool
|
||||||
|
net bool
|
||||||
|
ops *container.Ops
|
||||||
|
|
||||||
|
mnt []*vfs.MountInfoEntry
|
||||||
|
uid int
|
||||||
|
gid int
|
||||||
|
|
||||||
|
rules []seccomp.NativeRule
|
||||||
|
flags seccomp.ExportFlag
|
||||||
|
presets seccomp.FilterPreset
|
||||||
|
}{
|
||||||
|
{"minimal", true, false, false,
|
||||||
|
new(container.Ops), nil,
|
||||||
|
1000, 100, nil, 0, seccomp.PresetStrict},
|
||||||
|
{"allow", true, true, true,
|
||||||
|
new(container.Ops), nil,
|
||||||
|
1000, 100, nil, 0, seccomp.PresetExt | seccomp.PresetDenyDevel},
|
||||||
|
{"no filter", false, true, true,
|
||||||
|
new(container.Ops), nil,
|
||||||
|
1000, 100, nil, 0, seccomp.PresetExt},
|
||||||
|
{"custom rules", true, true, true,
|
||||||
|
new(container.Ops), nil,
|
||||||
|
1, 31, []seccomp.NativeRule{{seccomp.ScmpSyscall(syscall.SYS_SETUID), seccomp.ScmpErrno(syscall.EPERM), nil}}, 0, seccomp.PresetExt},
|
||||||
|
{"tmpfs", true, false, false,
|
||||||
|
new(container.Ops).
|
||||||
|
Tmpfs(hst.Tmp, 0, 0755),
|
||||||
|
[]*vfs.MountInfoEntry{
|
||||||
|
ent("/", hst.Tmp, "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
|
||||||
|
},
|
||||||
|
9, 9, nil, 0, seccomp.PresetStrict},
|
||||||
|
{"dev", true, true /* go test output is not a tty */, false,
|
||||||
|
new(container.Ops).
|
||||||
|
Dev("/dev").
|
||||||
|
Mqueue("/dev/mqueue"),
|
||||||
|
[]*vfs.MountInfoEntry{
|
||||||
|
ent("/", "/dev", "rw,nosuid,nodev,relatime", "tmpfs", "devtmpfs", ignore),
|
||||||
|
ent("/null", "/dev/null", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
|
ent("/zero", "/dev/zero", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
|
ent("/full", "/dev/full", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
|
ent("/random", "/dev/random", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
|
ent("/urandom", "/dev/urandom", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
|
ent("/tty", "/dev/tty", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
||||||
|
ent("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
|
||||||
|
ent("/", "/dev/mqueue", "rw,nosuid,nodev,noexec,relatime", "mqueue", "mqueue", "rw"),
|
||||||
|
},
|
||||||
|
1971, 100, nil, 0, seccomp.PresetStrict},
|
||||||
|
}
|
||||||
|
|
||||||
func TestContainer(t *testing.T) {
|
func TestContainer(t *testing.T) {
|
||||||
{
|
{
|
||||||
oldVerbose := hlog.Load()
|
oldVerbose := hlog.Load()
|
||||||
@@ -35,124 +92,86 @@ func TestContainer(t *testing.T) {
|
|||||||
t.Cleanup(func() { container.SetOutput(oldOutput) })
|
t.Cleanup(func() { container.SetOutput(oldOutput) })
|
||||||
}
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
t.Run("cancel", testContainerCancel(nil, func(t *testing.T, c *container.Container) {
|
||||||
name string
|
wantErr := context.Canceled
|
||||||
filter bool
|
wantExitCode := 0
|
||||||
session bool
|
if err := c.Wait(); !errors.Is(err, wantErr) {
|
||||||
net bool
|
hlog.PrintBaseError(err, "wait:")
|
||||||
ops *container.Ops
|
t.Errorf("Wait: error = %v, want %v", err, wantErr)
|
||||||
mnt []*vfs.MountInfoEntry
|
}
|
||||||
host string
|
if ps := c.ProcessState(); ps == nil {
|
||||||
rules []seccomp.NativeRule
|
t.Errorf("ProcessState unexpectedly returned nil")
|
||||||
flags seccomp.ExportFlag
|
} else if code := ps.ExitCode(); code != wantExitCode {
|
||||||
presets seccomp.FilterPreset
|
t.Errorf("ExitCode: %d, want %d", code, wantExitCode)
|
||||||
}{
|
}
|
||||||
{"minimal", true, false, false,
|
}))
|
||||||
new(container.Ops), nil, "test-minimal",
|
|
||||||
nil, 0, seccomp.PresetStrict},
|
|
||||||
{"allow", true, true, true,
|
|
||||||
new(container.Ops), nil, "test-minimal",
|
|
||||||
nil, 0, seccomp.PresetExt | seccomp.PresetDenyDevel},
|
|
||||||
{"no filter", false, true, true,
|
|
||||||
new(container.Ops), nil, "test-no-filter",
|
|
||||||
nil, 0, seccomp.PresetExt},
|
|
||||||
{"custom rules", true, true, true,
|
|
||||||
new(container.Ops), nil, "test-no-filter",
|
|
||||||
[]seccomp.NativeRule{
|
|
||||||
{seccomp.ScmpSyscall(syscall.SYS_SETUID), seccomp.ScmpErrno(syscall.EPERM), nil},
|
|
||||||
}, 0, seccomp.PresetExt},
|
|
||||||
{"tmpfs", true, false, false,
|
|
||||||
new(container.Ops).
|
|
||||||
Tmpfs(hst.Tmp, 0, 0755),
|
|
||||||
[]*vfs.MountInfoEntry{
|
|
||||||
e("/", hst.Tmp, "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
|
|
||||||
}, "test-tmpfs",
|
|
||||||
nil, 0, seccomp.PresetStrict},
|
|
||||||
{"dev", true, true /* go test output is not a tty */, false,
|
|
||||||
new(container.Ops).
|
|
||||||
Dev("/dev").
|
|
||||||
Mqueue("/dev/mqueue"),
|
|
||||||
[]*vfs.MountInfoEntry{
|
|
||||||
e("/", "/dev", "rw,nosuid,nodev,relatime", "tmpfs", "devtmpfs", ignore),
|
|
||||||
e("/null", "/dev/null", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
|
||||||
e("/zero", "/dev/zero", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
|
||||||
e("/full", "/dev/full", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
|
||||||
e("/random", "/dev/random", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
|
||||||
e("/urandom", "/dev/urandom", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
|
||||||
e("/tty", "/dev/tty", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
|
|
||||||
e("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
|
|
||||||
e("/", "/dev/mqueue", "rw,nosuid,nodev,noexec,relatime", "mqueue", "mqueue", "rw"),
|
|
||||||
}, "",
|
|
||||||
nil, 0, seccomp.PresetStrict},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
t.Run("forward", testContainerCancel(func(c *container.Container) {
|
||||||
|
c.ForwardCancel = true
|
||||||
|
}, func(t *testing.T, c *container.Container) {
|
||||||
|
var exitError *exec.ExitError
|
||||||
|
if err := c.Wait(); !errors.As(err, &exitError) {
|
||||||
|
hlog.PrintBaseError(err, "wait:")
|
||||||
|
t.Errorf("Wait: error = %v", err)
|
||||||
|
}
|
||||||
|
if code := exitError.ExitCode(); code != blockExitCodeInterrupt {
|
||||||
|
t.Errorf("ExitCode: %d, want %d", code, blockExitCodeInterrupt)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
for i, tc := range containerTestCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(t.Context(), helperDefaultTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
c := container.New(ctx, "/usr/bin/sandbox.test", "-test.v",
|
var libPaths []string
|
||||||
"-test.run=TestHelperCheckContainer", "--", "check", tc.host)
|
c := helperNewContainerLibPaths(ctx, &libPaths, "container", strconv.Itoa(i))
|
||||||
c.Uid = 1000
|
c.Uid = tc.uid
|
||||||
c.Gid = 100
|
c.Gid = tc.gid
|
||||||
c.Hostname = tc.host
|
c.Hostname = hostnameFromTestCase(tc.name)
|
||||||
c.CommandContext = commandContext
|
|
||||||
c.Stdout, c.Stderr = os.Stdout, os.Stderr
|
c.Stdout, c.Stderr = os.Stdout, os.Stderr
|
||||||
c.Ops = tc.ops
|
c.WaitDelay = helperDefaultTimeout
|
||||||
|
*c.Ops = append(*c.Ops, *tc.ops...)
|
||||||
c.SeccompRules = tc.rules
|
c.SeccompRules = tc.rules
|
||||||
c.SeccompFlags = tc.flags | seccomp.AllowMultiarch
|
c.SeccompFlags = tc.flags | seccomp.AllowMultiarch
|
||||||
c.SeccompPresets = tc.presets
|
c.SeccompPresets = tc.presets
|
||||||
c.SeccompDisable = !tc.filter
|
c.SeccompDisable = !tc.filter
|
||||||
c.RetainSession = tc.session
|
c.RetainSession = tc.session
|
||||||
c.HostNet = tc.net
|
c.HostNet = tc.net
|
||||||
if c.Args[5] == "" {
|
|
||||||
if name, err := os.Hostname(); err != nil {
|
|
||||||
t.Fatalf("cannot get hostname: %v", err)
|
|
||||||
} else {
|
|
||||||
c.Args[5] = name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.
|
c.
|
||||||
Tmpfs("/tmp", 0, 0755).
|
Tmpfs("/tmp", 0, 0755).
|
||||||
Bind(os.Args[0], os.Args[0], 0).
|
Place("/etc/hostname", []byte(c.Hostname))
|
||||||
Mkdir("/usr/bin", 0755).
|
|
||||||
Link(os.Args[0], "/usr/bin/sandbox.test").
|
|
||||||
Place("/etc/hostname", []byte(c.Args[5]))
|
|
||||||
// in case test has cgo enabled
|
|
||||||
var libPaths []string
|
|
||||||
if entries, err := ldd.ExecFilter(ctx,
|
|
||||||
commandContext,
|
|
||||||
func(v []byte) []byte {
|
|
||||||
return bytes.SplitN(v, []byte("TestHelperInit\n"), 2)[1]
|
|
||||||
}, os.Args[0]); err != nil {
|
|
||||||
log.Fatalf("ldd: %v", err)
|
|
||||||
} else {
|
|
||||||
libPaths = ldd.Path(entries)
|
|
||||||
}
|
|
||||||
for _, name := range libPaths {
|
|
||||||
c.Bind(name, name, 0)
|
|
||||||
}
|
|
||||||
// needs /proc to check mountinfo
|
// needs /proc to check mountinfo
|
||||||
c.Proc("/proc")
|
c.Proc("/proc")
|
||||||
|
|
||||||
|
// mountinfo cannot be resolved directly by helper due to libPaths nondeterminism
|
||||||
mnt := make([]*vfs.MountInfoEntry, 0, 3+len(libPaths))
|
mnt := make([]*vfs.MountInfoEntry, 0, 3+len(libPaths))
|
||||||
mnt = append(mnt, e("/sysroot", "/", "rw,nosuid,nodev,relatime", "tmpfs", "rootfs", ignore))
|
|
||||||
mnt = append(mnt, tc.mnt...)
|
|
||||||
mnt = append(mnt,
|
mnt = append(mnt,
|
||||||
e("/", "/tmp", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
|
ent("/sysroot", "/", "rw,nosuid,nodev,relatime", "tmpfs", "rootfs", ignore),
|
||||||
e(ignore, os.Args[0], "ro,nosuid,nodev,relatime", ignore, ignore, ignore),
|
// Bind(os.Args[0], helperInnerPath, 0)
|
||||||
e(ignore, "/etc/hostname", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", ignore),
|
ent(ignore, helperInnerPath, "ro,nosuid,nodev,relatime", ignore, ignore, ignore),
|
||||||
)
|
)
|
||||||
for _, name := range libPaths {
|
for _, name := range libPaths {
|
||||||
mnt = append(mnt, e(ignore, name, "ro,nosuid,nodev,relatime", ignore, ignore, ignore))
|
// Bind(name, name, 0)
|
||||||
|
mnt = append(mnt, ent(ignore, name, "ro,nosuid,nodev,relatime", ignore, ignore, ignore))
|
||||||
}
|
}
|
||||||
mnt = append(mnt, e("/", "/proc", "rw,nosuid,nodev,noexec,relatime", "proc", "proc", "rw"))
|
mnt = append(mnt, tc.mnt...)
|
||||||
|
mnt = append(mnt,
|
||||||
|
// Tmpfs("/tmp", 0, 0755)
|
||||||
|
ent("/", "/tmp", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
|
||||||
|
// Place("/etc/hostname", []byte(hostname))
|
||||||
|
ent(ignore, "/etc/hostname", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", ignore),
|
||||||
|
// Proc("/proc")
|
||||||
|
ent("/", "/proc", "rw,nosuid,nodev,noexec,relatime", "proc", "proc", "rw"),
|
||||||
|
// Place(pathWantMnt, want.Bytes())
|
||||||
|
ent(ignore, pathWantMnt, "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", ignore),
|
||||||
|
)
|
||||||
want := new(bytes.Buffer)
|
want := new(bytes.Buffer)
|
||||||
if err := gob.NewEncoder(want).Encode(mnt); err != nil {
|
if err := gob.NewEncoder(want).Encode(mnt); err != nil {
|
||||||
t.Fatalf("cannot serialise expected mount points: %v", err)
|
t.Fatalf("cannot serialise expected mount points: %v", err)
|
||||||
}
|
}
|
||||||
c.Stdin = want
|
c.Place(pathWantMnt, want.Bytes())
|
||||||
|
|
||||||
if err := c.Start(); err != nil {
|
if err := c.Start(); err != nil {
|
||||||
hlog.PrintBaseError(err, "start:")
|
hlog.PrintBaseError(err, "start:")
|
||||||
@@ -169,7 +188,7 @@ func TestContainer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func e(root, target, vfsOptstr, fsType, source, fsOptstr string) *vfs.MountInfoEntry {
|
func ent(root, target, vfsOptstr, fsType, source, fsOptstr string) *vfs.MountInfoEntry {
|
||||||
return &vfs.MountInfoEntry{
|
return &vfs.MountInfoEntry{
|
||||||
ID: ignoreV,
|
ID: ignoreV,
|
||||||
Parent: ignoreV,
|
Parent: ignoreV,
|
||||||
@@ -184,6 +203,50 @@ func e(root, target, vfsOptstr, fsType, source, fsOptstr string) *vfs.MountInfoE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hostnameFromTestCase(name string) string {
|
||||||
|
return "test-" + strings.Join(strings.Fields(name), "-")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testContainerCancel(
|
||||||
|
containerExtra func(c *container.Container),
|
||||||
|
waitCheck func(t *testing.T, c *container.Container),
|
||||||
|
) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithTimeout(t.Context(), helperDefaultTimeout)
|
||||||
|
|
||||||
|
c := helperNewContainer(ctx, "block")
|
||||||
|
c.Stdout, c.Stderr = os.Stdout, os.Stderr
|
||||||
|
c.WaitDelay = helperDefaultTimeout
|
||||||
|
if containerExtra != nil {
|
||||||
|
containerExtra(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
ready := make(chan struct{})
|
||||||
|
if r, w, err := os.Pipe(); err != nil {
|
||||||
|
t.Fatalf("cannot pipe: %v", err)
|
||||||
|
} else {
|
||||||
|
c.ExtraFiles = append(c.ExtraFiles, w)
|
||||||
|
go func() {
|
||||||
|
defer close(ready)
|
||||||
|
if _, err = r.Read(make([]byte, 1)); err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Start(); err != nil {
|
||||||
|
hlog.PrintBaseError(err, "start:")
|
||||||
|
t.Fatalf("cannot start container: %v", err)
|
||||||
|
} else if err = c.Serve(); err != nil {
|
||||||
|
hlog.PrintBaseError(err, "serve:")
|
||||||
|
t.Errorf("cannot serve setup params: %v", err)
|
||||||
|
}
|
||||||
|
<-ready
|
||||||
|
cancel()
|
||||||
|
waitCheck(t, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestContainerString(t *testing.T) {
|
func TestContainerString(t *testing.T) {
|
||||||
c := container.New(t.Context(), "ldd", "/usr/bin/env")
|
c := container.New(t.Context(), "ldd", "/usr/bin/env")
|
||||||
c.SeccompFlags |= seccomp.AllowMultiarch
|
c.SeccompFlags |= seccomp.AllowMultiarch
|
||||||
@@ -197,85 +260,109 @@ func TestContainerString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHelperInit(t *testing.T) {
|
const (
|
||||||
if len(os.Args) != 5 || os.Args[4] != "init" {
|
blockExitCodeInterrupt = 2
|
||||||
return
|
)
|
||||||
}
|
|
||||||
container.SetOutput(hlog.Output{})
|
|
||||||
container.Init(hlog.Prepare, internal.InstallOutput)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHelperCheckContainer(t *testing.T) {
|
func init() {
|
||||||
if len(os.Args) != 6 || os.Args[4] != "check" {
|
helperCommands = append(helperCommands, func(c command.Command) {
|
||||||
return
|
c.Command("block", command.UsageInternal, func(args []string) error {
|
||||||
}
|
if _, err := os.NewFile(3, "sync").Write([]byte{0}); err != nil {
|
||||||
|
return fmt.Errorf("write to sync pipe: %v", err)
|
||||||
t.Run("user", func(t *testing.T) {
|
|
||||||
if uid := syscall.Getuid(); uid != 1000 {
|
|
||||||
t.Errorf("Getuid: %d, want 1000", uid)
|
|
||||||
}
|
|
||||||
if gid := syscall.Getgid(); gid != 100 {
|
|
||||||
t.Errorf("Getgid: %d, want 100", gid)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
t.Run("hostname", func(t *testing.T) {
|
|
||||||
if name, err := os.Hostname(); err != nil {
|
|
||||||
t.Fatalf("cannot get hostname: %v", err)
|
|
||||||
} else if name != os.Args[5] {
|
|
||||||
t.Errorf("Hostname: %q, want %q", name, os.Args[5])
|
|
||||||
}
|
|
||||||
|
|
||||||
if p, err := os.ReadFile("/etc/hostname"); err != nil {
|
|
||||||
t.Fatalf("%v", err)
|
|
||||||
} else if string(p) != os.Args[5] {
|
|
||||||
t.Errorf("/etc/hostname: %q, want %q", string(p), os.Args[5])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
t.Run("mount", func(t *testing.T) {
|
|
||||||
var mnt []*vfs.MountInfoEntry
|
|
||||||
if err := gob.NewDecoder(os.Stdin).Decode(&mnt); err != nil {
|
|
||||||
t.Fatalf("cannot receive expected mount points: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var d *vfs.MountInfoDecoder
|
|
||||||
if f, err := os.Open("/proc/self/mountinfo"); err != nil {
|
|
||||||
t.Fatalf("cannot open mountinfo: %v", err)
|
|
||||||
} else {
|
|
||||||
d = vfs.NewMountInfoDecoder(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
i := 0
|
|
||||||
for cur := range d.Entries() {
|
|
||||||
if i == len(mnt) {
|
|
||||||
t.Errorf("got more than %d entries", len(mnt))
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
sig := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sig, os.Interrupt)
|
||||||
|
go func() { <-sig; os.Exit(blockExitCodeInterrupt) }()
|
||||||
|
}
|
||||||
|
select {}
|
||||||
|
})
|
||||||
|
|
||||||
// ugly hack but should be reliable and is less likely to false negative than comparing by parsed flags
|
c.Command("container", command.UsageInternal, func(args []string) error {
|
||||||
cur.VfsOptstr = strings.TrimSuffix(cur.VfsOptstr, ",relatime")
|
if len(args) != 1 {
|
||||||
cur.VfsOptstr = strings.TrimSuffix(cur.VfsOptstr, ",noatime")
|
return syscall.EINVAL
|
||||||
mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ",relatime")
|
}
|
||||||
mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ",noatime")
|
tc := containerTestCases[0]
|
||||||
|
if i, err := strconv.Atoi(args[0]); err != nil {
|
||||||
if !cur.EqualWithIgnore(mnt[i], "\x00") {
|
return fmt.Errorf("cannot parse test case index: %v", err)
|
||||||
t.Errorf("[FAIL] %s", cur)
|
|
||||||
} else {
|
} else {
|
||||||
t.Logf("[ OK ] %s", cur)
|
tc = containerTestCases[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
i++
|
if uid := syscall.Getuid(); uid != tc.uid {
|
||||||
}
|
return fmt.Errorf("uid: %d, want %d", uid, tc.uid)
|
||||||
if err := d.Err(); err != nil {
|
}
|
||||||
t.Errorf("cannot parse mountinfo: %v", err)
|
if gid := syscall.Getgid(); gid != tc.gid {
|
||||||
}
|
return fmt.Errorf("gid: %d, want %d", gid, tc.gid)
|
||||||
|
}
|
||||||
|
|
||||||
if i != len(mnt) {
|
wantHost := hostnameFromTestCase(tc.name)
|
||||||
t.Errorf("got %d entries, want %d", i, len(mnt))
|
if host, err := os.Hostname(); err != nil {
|
||||||
}
|
return fmt.Errorf("cannot get hostname: %v", err)
|
||||||
|
} else if host != wantHost {
|
||||||
|
return fmt.Errorf("hostname: %q, want %q", host, wantHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p, err := os.ReadFile("/etc/hostname"); err != nil {
|
||||||
|
return fmt.Errorf("cannot read /etc/hostname: %v", err)
|
||||||
|
} else if string(p) != wantHost {
|
||||||
|
return fmt.Errorf("/etc/hostname: %q, want %q", string(p), wantHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var fail bool
|
||||||
|
|
||||||
|
var mnt []*vfs.MountInfoEntry
|
||||||
|
if f, err := os.Open(pathWantMnt); err != nil {
|
||||||
|
return fmt.Errorf("cannot open expected mount points: %v", err)
|
||||||
|
} else if err = gob.NewDecoder(f).Decode(&mnt); err != nil {
|
||||||
|
return fmt.Errorf("cannot parse expected mount points: %v", err)
|
||||||
|
} else if err = f.Close(); err != nil {
|
||||||
|
return fmt.Errorf("cannot close expected mount points: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var d *vfs.MountInfoDecoder
|
||||||
|
if f, err := os.Open("/proc/self/mountinfo"); err != nil {
|
||||||
|
return fmt.Errorf("cannot open mountinfo: %v", err)
|
||||||
|
} else {
|
||||||
|
d = vfs.NewMountInfoDecoder(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for cur := range d.Entries() {
|
||||||
|
if i == len(mnt) {
|
||||||
|
return fmt.Errorf("got more than %d entries", len(mnt))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ugly hack but should be reliable and is less likely to false negative than comparing by parsed flags
|
||||||
|
cur.VfsOptstr = strings.TrimSuffix(cur.VfsOptstr, ",relatime")
|
||||||
|
cur.VfsOptstr = strings.TrimSuffix(cur.VfsOptstr, ",noatime")
|
||||||
|
mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ",relatime")
|
||||||
|
mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ",noatime")
|
||||||
|
|
||||||
|
if !cur.EqualWithIgnore(mnt[i], "\x00") {
|
||||||
|
fail = true
|
||||||
|
log.Printf("[FAIL] %s", cur)
|
||||||
|
} else {
|
||||||
|
log.Printf("[ OK ] %s", cur)
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if err := d.Err(); err != nil {
|
||||||
|
return fmt.Errorf("cannot parse mountinfo: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i != len(mnt) {
|
||||||
|
return fmt.Errorf("got %d entries, want %d", i, len(mnt))
|
||||||
|
}
|
||||||
|
|
||||||
|
if fail {
|
||||||
|
return errors.New("one or more mountinfo entries do not match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func commandContext(ctx context.Context) *exec.Cmd {
|
|
||||||
return exec.CommandContext(ctx, os.Args[0], "-test.v",
|
|
||||||
"-test.run=TestHelperInit", "--", "init")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -17,11 +17,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// time to wait for linger processes after death of initial process
|
/* intermediate tmpfs mount point
|
||||||
residualProcessTimeout = 5 * time.Second
|
|
||||||
|
|
||||||
// intermediate tmpfs mount point
|
this path might seem like a weird choice, however there are many good reasons to use it:
|
||||||
basePath = "/tmp"
|
- the contents of this path is never exposed to the container:
|
||||||
|
the tmpfs root established here effectively becomes anonymous after pivot_root
|
||||||
|
- it is safe to assume this path exists and is a directory:
|
||||||
|
this program will not work correctly without a proper /proc and neither will most others
|
||||||
|
- this path belongs to the container init:
|
||||||
|
the container init is not any more privileged or trusted than the rest of the container
|
||||||
|
- this path is only accessible by init and root:
|
||||||
|
the container init sets SUID_DUMP_DISABLE and terminates if that fails;
|
||||||
|
|
||||||
|
it should be noted that none of this should become relevant at any point since the resulting
|
||||||
|
intermediate root tmpfs should be effectively anonymous */
|
||||||
|
intermediateHostPath = "/proc/self/fd"
|
||||||
|
|
||||||
// setup params file descriptor
|
// setup params file descriptor
|
||||||
setupEnv = "HAKUREI_SETUP"
|
setupEnv = "HAKUREI_SETUP"
|
||||||
@@ -124,10 +134,10 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := Mount("rootfs", basePath, "tmpfs", MS_NODEV|MS_NOSUID, ""); err != nil {
|
if err := Mount("rootfs", intermediateHostPath, "tmpfs", MS_NODEV|MS_NOSUID, ""); err != nil {
|
||||||
log.Fatalf("cannot mount intermediate root: %v", err)
|
log.Fatalf("cannot mount intermediate root: %v", err)
|
||||||
}
|
}
|
||||||
if err := os.Chdir(basePath); err != nil {
|
if err := os.Chdir(intermediateHostPath); err != nil {
|
||||||
log.Fatalf("cannot enter base path: %v", err)
|
log.Fatalf("cannot enter base path: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,8 +151,8 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
|||||||
if err := os.Mkdir(hostDir, 0755); err != nil {
|
if err := os.Mkdir(hostDir, 0755); err != nil {
|
||||||
log.Fatalf("%v", err)
|
log.Fatalf("%v", err)
|
||||||
}
|
}
|
||||||
// pivot_root uncovers basePath in hostDir
|
// pivot_root uncovers intermediateHostPath in hostDir
|
||||||
if err := PivotRoot(basePath, hostDir); err != nil {
|
if err := PivotRoot(intermediateHostPath, hostDir); err != nil {
|
||||||
log.Fatalf("cannot pivot into intermediate root: %v", err)
|
log.Fatalf("cannot pivot into intermediate root: %v", err)
|
||||||
}
|
}
|
||||||
if err := os.Chdir("/"); err != nil {
|
if err := os.Chdir("/"); err != nil {
|
||||||
@@ -198,7 +208,7 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, _, errno := Syscall(PR_SET_NO_NEW_PRIVS, 1, 0, 0); errno != 0 {
|
if _, _, errno := Syscall(SYS_PRCTL, PR_SET_NO_NEW_PRIVS, 1, 0); errno != 0 {
|
||||||
log.Fatalf("prctl(PR_SET_NO_NEW_PRIVS): %v", errno)
|
log.Fatalf("prctl(PR_SET_NO_NEW_PRIVS): %v", errno)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,13 +267,14 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
|||||||
cmd.ExtraFiles = extraFiles
|
cmd.ExtraFiles = extraFiles
|
||||||
cmd.Dir = params.Dir
|
cmd.Dir = params.Dir
|
||||||
|
|
||||||
|
msg.Verbosef("starting initial program %s", params.Path)
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
log.Fatalf("%v", err)
|
log.Fatalf("%v", err)
|
||||||
}
|
}
|
||||||
msg.Suspend()
|
msg.Suspend()
|
||||||
|
|
||||||
if err := closeSetup(); err != nil {
|
if err := closeSetup(); err != nil {
|
||||||
log.Println("cannot close setup pipe:", err)
|
log.Printf("cannot close setup pipe: %v", err)
|
||||||
// not fatal
|
// not fatal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,7 +308,7 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !errors.Is(err, ECHILD) {
|
if !errors.Is(err, ECHILD) {
|
||||||
log.Println("unexpected wait4 response:", err)
|
log.Printf("unexpected wait4 response: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
close(done)
|
close(done)
|
||||||
@@ -305,7 +316,7 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
|||||||
|
|
||||||
// handle signals to dump withheld messages
|
// handle signals to dump withheld messages
|
||||||
sig := make(chan os.Signal, 2)
|
sig := make(chan os.Signal, 2)
|
||||||
signal.Notify(sig, SIGINT, SIGTERM)
|
signal.Notify(sig, os.Interrupt, CancelSignal)
|
||||||
|
|
||||||
// closed after residualProcessTimeout has elapsed after initial process death
|
// closed after residualProcessTimeout has elapsed after initial process death
|
||||||
timeout := make(chan struct{})
|
timeout := make(chan struct{})
|
||||||
@@ -315,9 +326,16 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
|||||||
select {
|
select {
|
||||||
case s := <-sig:
|
case s := <-sig:
|
||||||
if msg.Resume() {
|
if msg.Resume() {
|
||||||
msg.Verbosef("terminating on %s after process start", s.String())
|
msg.Verbosef("%s after process start", s.String())
|
||||||
} else {
|
} else {
|
||||||
msg.Verbosef("terminating on %s", s.String())
|
msg.Verbosef("got %s", s.String())
|
||||||
|
}
|
||||||
|
if s == CancelSignal && params.ForwardCancel && cmd.Process != nil {
|
||||||
|
msg.Verbose("forwarding context cancellation")
|
||||||
|
if err := cmd.Process.Signal(os.Interrupt); err != nil {
|
||||||
|
log.Printf("cannot forward cancellation: %v", err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
case w := <-info:
|
case w := <-info:
|
||||||
@@ -337,10 +355,7 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
|||||||
msg.Verbosef("initial process exited with status %#x", w.wstatus)
|
msg.Verbosef("initial process exited with status %#x", w.wstatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() { time.Sleep(params.AdoptWaitDelay); close(timeout) }()
|
||||||
time.Sleep(residualProcessTimeout)
|
|
||||||
close(timeout)
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
case <-done:
|
case <-done:
|
||||||
msg.BeforeExit()
|
msg.BeforeExit()
|
||||||
@@ -353,9 +368,11 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const initName = "init"
|
||||||
|
|
||||||
// TryArgv0 calls [Init] if the last element of argv0 is "init".
|
// TryArgv0 calls [Init] if the last element of argv0 is "init".
|
||||||
func TryArgv0(v Msg, prepare func(prefix string), setVerbose func(verbose bool)) {
|
func TryArgv0(v Msg, prepare func(prefix string), setVerbose func(verbose bool)) {
|
||||||
if len(os.Args) > 0 && path.Base(os.Args[0]) == "init" {
|
if len(os.Args) > 0 && path.Base(os.Args[0]) == initName {
|
||||||
msg = v
|
msg = v
|
||||||
Init(prepare, setVerbose)
|
Init(prepare, setVerbose)
|
||||||
msg.BeforeExit()
|
msg.BeforeExit()
|
||||||
|
|||||||
69
container/init_test.go
Normal file
69
container/init_test.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package container_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"hakurei.app/command"
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/internal"
|
||||||
|
"hakurei.app/internal/hlog"
|
||||||
|
"hakurei.app/ldd"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
envDoCheck = "HAKUREI_TEST_DO_CHECK"
|
||||||
|
|
||||||
|
helperDefaultTimeout = 5 * time.Second
|
||||||
|
helperInnerPath = "/usr/bin/helper"
|
||||||
|
)
|
||||||
|
|
||||||
|
var helperCommands []func(c command.Command)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput)
|
||||||
|
|
||||||
|
if os.Getenv(envDoCheck) == "1" {
|
||||||
|
c := command.New(os.Stderr, log.Printf, "helper", func(args []string) error {
|
||||||
|
log.SetFlags(0)
|
||||||
|
log.SetPrefix("helper: ")
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
for _, f := range helperCommands {
|
||||||
|
f(c)
|
||||||
|
}
|
||||||
|
c.MustParse(os.Args[1:], func(err error) {
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
|
func helperNewContainerLibPaths(ctx context.Context, libPaths *[]string, args ...string) (c *container.Container) {
|
||||||
|
c = container.New(ctx, helperInnerPath, args...)
|
||||||
|
c.Env = append(c.Env, envDoCheck+"=1")
|
||||||
|
c.Bind(os.Args[0], helperInnerPath, 0)
|
||||||
|
|
||||||
|
// in case test has cgo enabled
|
||||||
|
if entries, err := ldd.Exec(ctx, os.Args[0]); err != nil {
|
||||||
|
log.Fatalf("ldd: %v", err)
|
||||||
|
} else {
|
||||||
|
*libPaths = ldd.Path(entries)
|
||||||
|
}
|
||||||
|
for _, name := range *libPaths {
|
||||||
|
c.Bind(name, name, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func helperNewContainer(ctx context.Context, args ...string) (c *container.Container) {
|
||||||
|
return helperNewContainerLibPaths(ctx, new([]string), args...)
|
||||||
|
}
|
||||||
124
container/ops.go
124
container/ops.go
@@ -15,7 +15,10 @@ import (
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
Ops []Op
|
Ops []Op
|
||||||
Op interface {
|
|
||||||
|
// Op is a generic setup step ran inside the container init.
|
||||||
|
// Implementations of this interface are sent as a stream of gobs.
|
||||||
|
Op interface {
|
||||||
// early is called in host root.
|
// early is called in host root.
|
||||||
early(params *Params) error
|
early(params *Params) error
|
||||||
// apply is called in intermediate root.
|
// apply is called in intermediate root.
|
||||||
@@ -27,11 +30,17 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Grow grows the slice Ops points to using [slices.Grow].
|
||||||
func (f *Ops) Grow(n int) { *f = slices.Grow(*f, n) }
|
func (f *Ops) Grow(n int) { *f = slices.Grow(*f, n) }
|
||||||
|
|
||||||
func init() { gob.Register(new(BindMountOp)) }
|
func init() { gob.Register(new(BindMountOp)) }
|
||||||
|
|
||||||
// BindMountOp bind mounts host path Source on container path Target.
|
// Bind appends an [Op] that bind mounts host path [BindMountOp.Source] on container path [BindMountOp.Target].
|
||||||
|
func (f *Ops) Bind(source, target string, flags int) *Ops {
|
||||||
|
*f = append(*f, &BindMountOp{source, "", target, flags})
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
type BindMountOp struct {
|
type BindMountOp struct {
|
||||||
Source, SourceFinal, Target string
|
Source, SourceFinal, Target string
|
||||||
|
|
||||||
@@ -39,8 +48,11 @@ type BindMountOp struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// BindOptional skips nonexistent host paths.
|
||||||
BindOptional = 1 << iota
|
BindOptional = 1 << iota
|
||||||
|
// BindWritable mounts filesystem read-write.
|
||||||
BindWritable
|
BindWritable
|
||||||
|
// BindDevice allows access to devices (special files) on this filesystem.
|
||||||
BindDevice
|
BindDevice
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -108,14 +120,15 @@ func (b *BindMountOp) String() string {
|
|||||||
}
|
}
|
||||||
return fmt.Sprintf("%q on %q flags %#x", b.Source, b.Target, b.Flags&BindWritable)
|
return fmt.Sprintf("%q on %q flags %#x", b.Source, b.Target, b.Flags&BindWritable)
|
||||||
}
|
}
|
||||||
func (f *Ops) Bind(source, target string, flags int) *Ops {
|
|
||||||
*f = append(*f, &BindMountOp{source, "", target, flags})
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { gob.Register(new(MountProcOp)) }
|
func init() { gob.Register(new(MountProcOp)) }
|
||||||
|
|
||||||
// MountProcOp mounts a private instance of proc.
|
// Proc appends an [Op] that mounts a private instance of proc.
|
||||||
|
func (f *Ops) Proc(dest string) *Ops {
|
||||||
|
*f = append(*f, MountProcOp(dest))
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
type MountProcOp string
|
type MountProcOp string
|
||||||
|
|
||||||
func (p MountProcOp) early(*Params) error { return nil }
|
func (p MountProcOp) early(*Params) error { return nil }
|
||||||
@@ -137,14 +150,15 @@ func (p MountProcOp) apply(params *Params) error {
|
|||||||
func (p MountProcOp) Is(op Op) bool { vp, ok := op.(MountProcOp); return ok && p == vp }
|
func (p MountProcOp) Is(op Op) bool { vp, ok := op.(MountProcOp); return ok && p == vp }
|
||||||
func (MountProcOp) prefix() string { return "mounting" }
|
func (MountProcOp) prefix() string { return "mounting" }
|
||||||
func (p MountProcOp) String() string { return fmt.Sprintf("proc on %q", string(p)) }
|
func (p MountProcOp) String() string { return fmt.Sprintf("proc on %q", string(p)) }
|
||||||
func (f *Ops) Proc(dest string) *Ops {
|
|
||||||
*f = append(*f, MountProcOp(dest))
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { gob.Register(new(MountDevOp)) }
|
func init() { gob.Register(new(MountDevOp)) }
|
||||||
|
|
||||||
// MountDevOp mounts part of host dev.
|
// Dev appends an [Op] that mounts a subset of host /dev.
|
||||||
|
func (f *Ops) Dev(dest string) *Ops {
|
||||||
|
*f = append(*f, MountDevOp(dest))
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
type MountDevOp string
|
type MountDevOp string
|
||||||
|
|
||||||
func (d MountDevOp) early(*Params) error { return nil }
|
func (d MountDevOp) early(*Params) error { return nil }
|
||||||
@@ -231,14 +245,15 @@ func (d MountDevOp) apply(params *Params) error {
|
|||||||
func (d MountDevOp) Is(op Op) bool { vd, ok := op.(MountDevOp); return ok && d == vd }
|
func (d MountDevOp) Is(op Op) bool { vd, ok := op.(MountDevOp); return ok && d == vd }
|
||||||
func (MountDevOp) prefix() string { return "mounting" }
|
func (MountDevOp) prefix() string { return "mounting" }
|
||||||
func (d MountDevOp) String() string { return fmt.Sprintf("dev on %q", string(d)) }
|
func (d MountDevOp) String() string { return fmt.Sprintf("dev on %q", string(d)) }
|
||||||
func (f *Ops) Dev(dest string) *Ops {
|
|
||||||
*f = append(*f, MountDevOp(dest))
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { gob.Register(new(MountMqueueOp)) }
|
func init() { gob.Register(new(MountMqueueOp)) }
|
||||||
|
|
||||||
// MountMqueueOp mounts a private mqueue instance on container Path.
|
// Mqueue appends an [Op] that mounts a private instance of mqueue.
|
||||||
|
func (f *Ops) Mqueue(dest string) *Ops {
|
||||||
|
*f = append(*f, MountMqueueOp(dest))
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
type MountMqueueOp string
|
type MountMqueueOp string
|
||||||
|
|
||||||
func (m MountMqueueOp) early(*Params) error { return nil }
|
func (m MountMqueueOp) early(*Params) error { return nil }
|
||||||
@@ -260,14 +275,15 @@ func (m MountMqueueOp) apply(params *Params) error {
|
|||||||
func (m MountMqueueOp) Is(op Op) bool { vm, ok := op.(MountMqueueOp); return ok && m == vm }
|
func (m MountMqueueOp) Is(op Op) bool { vm, ok := op.(MountMqueueOp); return ok && m == vm }
|
||||||
func (MountMqueueOp) prefix() string { return "mounting" }
|
func (MountMqueueOp) prefix() string { return "mounting" }
|
||||||
func (m MountMqueueOp) String() string { return fmt.Sprintf("mqueue on %q", string(m)) }
|
func (m MountMqueueOp) String() string { return fmt.Sprintf("mqueue on %q", string(m)) }
|
||||||
func (f *Ops) Mqueue(dest string) *Ops {
|
|
||||||
*f = append(*f, MountMqueueOp(dest))
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { gob.Register(new(MountTmpfsOp)) }
|
func init() { gob.Register(new(MountTmpfsOp)) }
|
||||||
|
|
||||||
// MountTmpfsOp mounts tmpfs on container Path.
|
// Tmpfs appends an [Op] that mounts tmpfs on container path [MountTmpfsOp.Path].
|
||||||
|
func (f *Ops) Tmpfs(dest string, size int, perm os.FileMode) *Ops {
|
||||||
|
*f = append(*f, &MountTmpfsOp{dest, size, perm})
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
type MountTmpfsOp struct {
|
type MountTmpfsOp struct {
|
||||||
Path string
|
Path string
|
||||||
Size int
|
Size int
|
||||||
@@ -288,14 +304,15 @@ func (t *MountTmpfsOp) apply(*Params) error {
|
|||||||
func (t *MountTmpfsOp) Is(op Op) bool { vt, ok := op.(*MountTmpfsOp); return ok && *t == *vt }
|
func (t *MountTmpfsOp) Is(op Op) bool { vt, ok := op.(*MountTmpfsOp); return ok && *t == *vt }
|
||||||
func (*MountTmpfsOp) prefix() string { return "mounting" }
|
func (*MountTmpfsOp) prefix() string { return "mounting" }
|
||||||
func (t *MountTmpfsOp) String() string { return fmt.Sprintf("tmpfs on %q size %d", t.Path, t.Size) }
|
func (t *MountTmpfsOp) String() string { return fmt.Sprintf("tmpfs on %q size %d", t.Path, t.Size) }
|
||||||
func (f *Ops) Tmpfs(dest string, size int, perm os.FileMode) *Ops {
|
|
||||||
*f = append(*f, &MountTmpfsOp{dest, size, perm})
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { gob.Register(new(SymlinkOp)) }
|
func init() { gob.Register(new(SymlinkOp)) }
|
||||||
|
|
||||||
// SymlinkOp creates a symlink in the container filesystem.
|
// Link appends an [Op] that creates a symlink in the container filesystem.
|
||||||
|
func (f *Ops) Link(target, linkName string) *Ops {
|
||||||
|
*f = append(*f, &SymlinkOp{target, linkName})
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
type SymlinkOp [2]string
|
type SymlinkOp [2]string
|
||||||
|
|
||||||
func (l *SymlinkOp) early(*Params) error {
|
func (l *SymlinkOp) early(*Params) error {
|
||||||
@@ -331,14 +348,15 @@ func (l *SymlinkOp) apply(params *Params) error {
|
|||||||
func (l *SymlinkOp) Is(op Op) bool { vl, ok := op.(*SymlinkOp); return ok && *l == *vl }
|
func (l *SymlinkOp) Is(op Op) bool { vl, ok := op.(*SymlinkOp); return ok && *l == *vl }
|
||||||
func (*SymlinkOp) prefix() string { return "creating" }
|
func (*SymlinkOp) prefix() string { return "creating" }
|
||||||
func (l *SymlinkOp) String() string { return fmt.Sprintf("symlink on %q target %q", l[1], l[0]) }
|
func (l *SymlinkOp) String() string { return fmt.Sprintf("symlink on %q target %q", l[1], l[0]) }
|
||||||
func (f *Ops) Link(target, linkName string) *Ops {
|
|
||||||
*f = append(*f, &SymlinkOp{target, linkName})
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { gob.Register(new(MkdirOp)) }
|
func init() { gob.Register(new(MkdirOp)) }
|
||||||
|
|
||||||
// MkdirOp creates a directory in the container filesystem.
|
// Mkdir appends an [Op] that creates a directory in the container filesystem.
|
||||||
|
func (f *Ops) Mkdir(dest string, perm os.FileMode) *Ops {
|
||||||
|
*f = append(*f, &MkdirOp{dest, perm})
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
type MkdirOp struct {
|
type MkdirOp struct {
|
||||||
Path string
|
Path string
|
||||||
Perm os.FileMode
|
Perm os.FileMode
|
||||||
@@ -359,14 +377,21 @@ func (m *MkdirOp) apply(*Params) error {
|
|||||||
func (m *MkdirOp) Is(op Op) bool { vm, ok := op.(*MkdirOp); return ok && m == vm }
|
func (m *MkdirOp) Is(op Op) bool { vm, ok := op.(*MkdirOp); return ok && m == vm }
|
||||||
func (*MkdirOp) prefix() string { return "creating" }
|
func (*MkdirOp) prefix() string { return "creating" }
|
||||||
func (m *MkdirOp) String() string { return fmt.Sprintf("directory %q perm %s", m.Path, m.Perm) }
|
func (m *MkdirOp) String() string { return fmt.Sprintf("directory %q perm %s", m.Path, m.Perm) }
|
||||||
func (f *Ops) Mkdir(dest string, perm os.FileMode) *Ops {
|
|
||||||
*f = append(*f, &MkdirOp{dest, perm})
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { gob.Register(new(TmpfileOp)) }
|
func init() { gob.Register(new(TmpfileOp)) }
|
||||||
|
|
||||||
// TmpfileOp places a file in container Path containing Data.
|
// Place appends an [Op] that places a file in container path [TmpfileOp.Path] containing [TmpfileOp.Data].
|
||||||
|
func (f *Ops) Place(name string, data []byte) *Ops { *f = append(*f, &TmpfileOp{name, data}); return f }
|
||||||
|
|
||||||
|
// PlaceP is like Place but writes the address of [TmpfileOp.Data] to the pointer dataP points to.
|
||||||
|
func (f *Ops) PlaceP(name string, dataP **[]byte) *Ops {
|
||||||
|
t := &TmpfileOp{Path: name}
|
||||||
|
*dataP = &t.Data
|
||||||
|
|
||||||
|
*f = append(*f, t)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
type TmpfileOp struct {
|
type TmpfileOp struct {
|
||||||
Path string
|
Path string
|
||||||
Data []byte
|
Data []byte
|
||||||
@@ -415,19 +440,19 @@ func (*TmpfileOp) prefix() string { return "placing" }
|
|||||||
func (t *TmpfileOp) String() string {
|
func (t *TmpfileOp) String() string {
|
||||||
return fmt.Sprintf("tmpfile %q (%d bytes)", t.Path, len(t.Data))
|
return fmt.Sprintf("tmpfile %q (%d bytes)", t.Path, len(t.Data))
|
||||||
}
|
}
|
||||||
func (f *Ops) Place(name string, data []byte) *Ops { *f = append(*f, &TmpfileOp{name, data}); return f }
|
|
||||||
func (f *Ops) PlaceP(name string, dataP **[]byte) *Ops {
|
|
||||||
t := &TmpfileOp{Path: name}
|
|
||||||
*dataP = &t.Data
|
|
||||||
|
|
||||||
*f = append(*f, t)
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { gob.Register(new(AutoEtcOp)) }
|
func init() { gob.Register(new(AutoEtcOp)) }
|
||||||
|
|
||||||
// AutoEtcOp expands host /etc into a toplevel symlink mirror with /etc semantics.
|
// Etc appends an [Op] that expands host /etc into a toplevel symlink mirror with /etc semantics.
|
||||||
// This is not a generic setup op. It is implemented here to reduce ipc overhead.
|
// This is not a generic setup op. It is implemented here to reduce ipc overhead.
|
||||||
|
func (f *Ops) Etc(host, prefix string) *Ops {
|
||||||
|
e := &AutoEtcOp{prefix}
|
||||||
|
f.Mkdir("/etc", 0755)
|
||||||
|
f.Bind(host, e.hostPath(), 0)
|
||||||
|
*f = append(*f, e)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
type AutoEtcOp struct{ Prefix string }
|
type AutoEtcOp struct{ Prefix string }
|
||||||
|
|
||||||
func (e *AutoEtcOp) early(*Params) error { return nil }
|
func (e *AutoEtcOp) early(*Params) error { return nil }
|
||||||
@@ -473,10 +498,3 @@ func (e *AutoEtcOp) Is(op Op) bool {
|
|||||||
}
|
}
|
||||||
func (*AutoEtcOp) prefix() string { return "setting up" }
|
func (*AutoEtcOp) prefix() string { return "setting up" }
|
||||||
func (e *AutoEtcOp) String() string { return fmt.Sprintf("auto etc %s", e.Prefix) }
|
func (e *AutoEtcOp) String() string { return fmt.Sprintf("auto etc %s", e.Prefix) }
|
||||||
func (f *Ops) Etc(host, prefix string) *Ops {
|
|
||||||
e := &AutoEtcOp{prefix}
|
|
||||||
f.Mkdir("/etc", 0755)
|
|
||||||
f.Bind(host, e.hostPath(), 0)
|
|
||||||
*f = append(*f, e)
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|||||||
24
container/seccomp/hash_amd64_test.go
Normal file
24
container/seccomp/hash_amd64_test.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package seccomp_test
|
||||||
|
|
||||||
|
import . "hakurei.app/container/seccomp"
|
||||||
|
|
||||||
|
var bpfExpected = bpfLookup{
|
||||||
|
{AllowMultiarch | AllowCAN |
|
||||||
|
AllowBluetooth, PresetExt |
|
||||||
|
PresetDenyNS | PresetDenyTTY | PresetDenyDevel |
|
||||||
|
PresetLinux32}: toHash(
|
||||||
|
"e99dd345e195413473d3cbee07b4ed57b908bfa89ea2072fe93482847f50b5b758da17e74ca2bbc00813de49a2b9bf834c024ed48850be69b68a9a4c5f53a9db"),
|
||||||
|
|
||||||
|
{0, 0}: toHash(
|
||||||
|
"95ec69d017733e072160e0da80fdebecdf27ae8166f5e2a731270c98ea2d2946cb5231029063668af215879155da21aca79b070e04c0ee9acdf58f55cfa815a5"),
|
||||||
|
{0, PresetExt}: toHash(
|
||||||
|
"dc7f2e1c5e829b79ebb7efc759150f54a83a75c8df6fee4dce5dadc4736c585d4deebfeb3c7969af3a077e90b77bb4741db05d90997c8659b95891206ac9952d"),
|
||||||
|
{0, PresetStrict}: toHash(
|
||||||
|
"e880298df2bd6751d0040fc21bc0ed4c00f95dc0d7ba506c244d8b8cf6866dba8ef4a33296f287b66cccc1d78e97026597f84cc7dec1573e148960fbd35cd735"),
|
||||||
|
{0, PresetDenyNS | PresetDenyTTY | PresetDenyDevel}: toHash(
|
||||||
|
"39871b93ffafc8b979fcedc0b0c37b9e03922f5b02748dc5c3c17c92527f6e022ede1f48bff59246ea452c0d1de54827808b1a6f84f32bbde1aa02ae30eedcfa"),
|
||||||
|
{0, PresetExt | PresetDenyDevel}: toHash(
|
||||||
|
"c698b081ff957afe17a6d94374537d37f2a63f6f9dd75da7546542407a9e32476ebda3312ba7785d7f618542bcfaf27ca27dcc2dddba852069d28bcfe8cad39a"),
|
||||||
|
{0, PresetExt | PresetDenyNS | PresetDenyDevel}: toHash(
|
||||||
|
"0b76007476c1c9e25dbf674c29fdf609a1656a70063e49327654e1b5360ad3da06e1a3e32bf80e961c5516ad83d4b9e7e9bde876a93797e27627d2555c25858b"),
|
||||||
|
}
|
||||||
24
container/seccomp/hash_arm64_test.go
Normal file
24
container/seccomp/hash_arm64_test.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package seccomp_test
|
||||||
|
|
||||||
|
import . "hakurei.app/container/seccomp"
|
||||||
|
|
||||||
|
var bpfExpected = bpfLookup{
|
||||||
|
{AllowMultiarch | AllowCAN |
|
||||||
|
AllowBluetooth, PresetExt |
|
||||||
|
PresetDenyNS | PresetDenyTTY | PresetDenyDevel |
|
||||||
|
PresetLinux32}: toHash(
|
||||||
|
"1431c013f2ddac3adae577821cb5d351b1514e7c754d62346ddffd31f46ea02fb368e46e3f8104f81019617e721fe687ddd83f1e79580622ccc991da12622170"),
|
||||||
|
|
||||||
|
{0, 0}: toHash(
|
||||||
|
"450c21210dbf124dfa7ae56d0130f9c2e24b26f5bce8795ee75766c75850438ff9e7d91c5e73d63bbe51a5d4b06c2a0791c4de2903b2b9805f16265318183235"),
|
||||||
|
{0, PresetExt}: toHash(
|
||||||
|
"d971d0f2d30f54ac920fc6d84df2be279e9fd28cf2d48be775d7fdbd790b750e1369401cd3bb8bcf9ba3adb91874fe9792d9e3f62209b8ee59c9fdd2ddd10c7b"),
|
||||||
|
{0, PresetStrict}: toHash(
|
||||||
|
"79318538a3dc851314b6bd96f10d5861acb2aa7e13cb8de0619d0f6a76709d67f01ef3fd67e195862b02f9711e5b769bc4d1eb4fc0dfc41a723c89c968a93297"),
|
||||||
|
{0, PresetDenyNS | PresetDenyTTY | PresetDenyDevel}: toHash(
|
||||||
|
"228286c2f5df8e44463be0a57b91977b7f38b63b09e5d98dfabe5c61545b8f9ac3e5ea3d86df55d7edf2ce61875f0a5a85c0ab82800bef178c42533e8bdc9a6c"),
|
||||||
|
{0, PresetExt | PresetDenyDevel}: toHash(
|
||||||
|
"433ce9b911282d6dcc8029319fb79b816b60d5a795ec8fc94344dd027614d68f023166a91bb881faaeeedd26e3d89474e141e5a69a97e93b8984ca8f14999980"),
|
||||||
|
{0, PresetExt | PresetDenyNS | PresetDenyDevel}: toHash(
|
||||||
|
"cf1f4dc87436ba8ec95d268b663a6397bb0b4a5ac64d8557e6cc529d8b0f6f65dad3a92b62ed29d85eee9c6dde1267757a4d0f86032e8a45ca1bceadfa34cf5e"),
|
||||||
|
}
|
||||||
28
container/seccomp/hash_test.go
Normal file
28
container/seccomp/hash_test.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package seccomp_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
|
"hakurei.app/container/seccomp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
bpfPreset = struct {
|
||||||
|
seccomp.ExportFlag
|
||||||
|
seccomp.FilterPreset
|
||||||
|
}
|
||||||
|
bpfLookup map[bpfPreset][]byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func toHash(s string) []byte {
|
||||||
|
if len(s) != 128 {
|
||||||
|
panic("bad sha512 string length")
|
||||||
|
}
|
||||||
|
if v, err := hex.DecodeString(s); err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
} else if len(v) != 64 {
|
||||||
|
panic("unreachable")
|
||||||
|
} else {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ package seccomp
|
|||||||
#cgo linux pkg-config: --static libseccomp
|
#cgo linux pkg-config: --static libseccomp
|
||||||
|
|
||||||
#include <libseccomp-helper.h>
|
#include <libseccomp-helper.h>
|
||||||
|
#include <sys/personality.h>
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
@@ -14,6 +15,11 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PER_LINUX = C.PER_LINUX
|
||||||
|
PER_LINUX32 = C.PER_LINUX32
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrInvalidRules = errors.New("invalid native rules slice")
|
ErrInvalidRules = errors.New("invalid native rules slice")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,81 +14,28 @@ import (
|
|||||||
func TestExport(t *testing.T) {
|
func TestExport(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
presets FilterPreset
|
|
||||||
flags ExportFlag
|
flags ExportFlag
|
||||||
want []byte
|
presets FilterPreset
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"compat", 0, 0, []byte{
|
{"everything", AllowMultiarch | AllowCAN |
|
||||||
0x95, 0xec, 0x69, 0xd0, 0x17, 0x73, 0x3e, 0x07,
|
AllowBluetooth, PresetExt |
|
||||||
0x21, 0x60, 0xe0, 0xda, 0x80, 0xfd, 0xeb, 0xec,
|
|
||||||
0xdf, 0x27, 0xae, 0x81, 0x66, 0xf5, 0xe2, 0xa7,
|
|
||||||
0x31, 0x27, 0x0c, 0x98, 0xea, 0x2d, 0x29, 0x46,
|
|
||||||
0xcb, 0x52, 0x31, 0x02, 0x90, 0x63, 0x66, 0x8a,
|
|
||||||
0xf2, 0x15, 0x87, 0x91, 0x55, 0xda, 0x21, 0xac,
|
|
||||||
0xa7, 0x9b, 0x07, 0x0e, 0x04, 0xc0, 0xee, 0x9a,
|
|
||||||
0xcd, 0xf5, 0x8f, 0x55, 0xcf, 0xa8, 0x15, 0xa5,
|
|
||||||
}, false},
|
|
||||||
{"base", PresetExt, 0, []byte{
|
|
||||||
0xdc, 0x7f, 0x2e, 0x1c, 0x5e, 0x82, 0x9b, 0x79,
|
|
||||||
0xeb, 0xb7, 0xef, 0xc7, 0x59, 0x15, 0x0f, 0x54,
|
|
||||||
0xa8, 0x3a, 0x75, 0xc8, 0xdf, 0x6f, 0xee, 0x4d,
|
|
||||||
0xce, 0x5d, 0xad, 0xc4, 0x73, 0x6c, 0x58, 0x5d,
|
|
||||||
0x4d, 0xee, 0xbf, 0xeb, 0x3c, 0x79, 0x69, 0xaf,
|
|
||||||
0x3a, 0x07, 0x7e, 0x90, 0xb7, 0x7b, 0xb4, 0x74,
|
|
||||||
0x1d, 0xb0, 0x5d, 0x90, 0x99, 0x7c, 0x86, 0x59,
|
|
||||||
0xb9, 0x58, 0x91, 0x20, 0x6a, 0xc9, 0x95, 0x2d,
|
|
||||||
}, false},
|
|
||||||
{"everything", PresetExt |
|
|
||||||
PresetDenyNS | PresetDenyTTY | PresetDenyDevel |
|
PresetDenyNS | PresetDenyTTY | PresetDenyDevel |
|
||||||
PresetLinux32, AllowMultiarch | AllowCAN |
|
PresetLinux32, false},
|
||||||
AllowBluetooth, []byte{
|
|
||||||
0xe9, 0x9d, 0xd3, 0x45, 0xe1, 0x95, 0x41, 0x34,
|
{"compat", 0, 0, false},
|
||||||
0x73, 0xd3, 0xcb, 0xee, 0x07, 0xb4, 0xed, 0x57,
|
{"base", 0, PresetExt, false},
|
||||||
0xb9, 0x08, 0xbf, 0xa8, 0x9e, 0xa2, 0x07, 0x2f,
|
{"strict", 0, PresetStrict, false},
|
||||||
0xe9, 0x34, 0x82, 0x84, 0x7f, 0x50, 0xb5, 0xb7,
|
{"strict compat", 0, PresetDenyNS | PresetDenyTTY | PresetDenyDevel, false},
|
||||||
0x58, 0xda, 0x17, 0xe7, 0x4c, 0xa2, 0xbb, 0xc0,
|
{"hakurei default", 0, PresetExt | PresetDenyDevel, false},
|
||||||
0x08, 0x13, 0xde, 0x49, 0xa2, 0xb9, 0xbf, 0x83,
|
{"hakurei tty", 0, PresetExt | PresetDenyNS | PresetDenyDevel, false},
|
||||||
0x4c, 0x02, 0x4e, 0xd4, 0x88, 0x50, 0xbe, 0x69,
|
|
||||||
0xb6, 0x8a, 0x9a, 0x4c, 0x5f, 0x53, 0xa9, 0xdb,
|
|
||||||
}, false},
|
|
||||||
{"strict", PresetStrict, 0, []byte{
|
|
||||||
0xe8, 0x80, 0x29, 0x8d, 0xf2, 0xbd, 0x67, 0x51,
|
|
||||||
0xd0, 0x04, 0x0f, 0xc2, 0x1b, 0xc0, 0xed, 0x4c,
|
|
||||||
0x00, 0xf9, 0x5d, 0xc0, 0xd7, 0xba, 0x50, 0x6c,
|
|
||||||
0x24, 0x4d, 0x8b, 0x8c, 0xf6, 0x86, 0x6d, 0xba,
|
|
||||||
0x8e, 0xf4, 0xa3, 0x32, 0x96, 0xf2, 0x87, 0xb6,
|
|
||||||
0x6c, 0xcc, 0xc1, 0xd7, 0x8e, 0x97, 0x02, 0x65,
|
|
||||||
0x97, 0xf8, 0x4c, 0xc7, 0xde, 0xc1, 0x57, 0x3e,
|
|
||||||
0x14, 0x89, 0x60, 0xfb, 0xd3, 0x5c, 0xd7, 0x35,
|
|
||||||
}, false},
|
|
||||||
{"strict compat", 0 |
|
|
||||||
PresetDenyNS | PresetDenyTTY | PresetDenyDevel, 0, []byte{
|
|
||||||
0x39, 0x87, 0x1b, 0x93, 0xff, 0xaf, 0xc8, 0xb9,
|
|
||||||
0x79, 0xfc, 0xed, 0xc0, 0xb0, 0xc3, 0x7b, 0x9e,
|
|
||||||
0x03, 0x92, 0x2f, 0x5b, 0x02, 0x74, 0x8d, 0xc5,
|
|
||||||
0xc3, 0xc1, 0x7c, 0x92, 0x52, 0x7f, 0x6e, 0x02,
|
|
||||||
0x2e, 0xde, 0x1f, 0x48, 0xbf, 0xf5, 0x92, 0x46,
|
|
||||||
0xea, 0x45, 0x2c, 0x0d, 0x1d, 0xe5, 0x48, 0x27,
|
|
||||||
0x80, 0x8b, 0x1a, 0x6f, 0x84, 0xf3, 0x2b, 0xbd,
|
|
||||||
0xe1, 0xaa, 0x02, 0xae, 0x30, 0xee, 0xdc, 0xfa,
|
|
||||||
}, false},
|
|
||||||
{"hakurei default", PresetExt | PresetDenyDevel, 0, []byte{
|
|
||||||
0xc6, 0x98, 0xb0, 0x81, 0xff, 0x95, 0x7a, 0xfe,
|
|
||||||
0x17, 0xa6, 0xd9, 0x43, 0x74, 0x53, 0x7d, 0x37,
|
|
||||||
0xf2, 0xa6, 0x3f, 0x6f, 0x9d, 0xd7, 0x5d, 0xa7,
|
|
||||||
0x54, 0x65, 0x42, 0x40, 0x7a, 0x9e, 0x32, 0x47,
|
|
||||||
0x6e, 0xbd, 0xa3, 0x31, 0x2b, 0xa7, 0x78, 0x5d,
|
|
||||||
0x7f, 0x61, 0x85, 0x42, 0xbc, 0xfa, 0xf2, 0x7c,
|
|
||||||
0xa2, 0x7d, 0xcc, 0x2d, 0xdd, 0xba, 0x85, 0x20,
|
|
||||||
0x69, 0xd2, 0x8b, 0xcf, 0xe8, 0xca, 0xd3, 0x9a,
|
|
||||||
}, false},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := make([]byte, 8)
|
buf := make([]byte, 8)
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
e := New(Preset(tc.presets, tc.flags), tc.flags)
|
e := New(Preset(tc.presets, tc.flags), tc.flags)
|
||||||
|
want := bpfExpected[bpfPreset{tc.flags, tc.presets}]
|
||||||
digest := sha512.New()
|
digest := sha512.New()
|
||||||
|
|
||||||
if _, err := io.CopyBuffer(digest, e, buf); (err != nil) != tc.wantErr {
|
if _, err := io.CopyBuffer(digest, e, buf); (err != nil) != tc.wantErr {
|
||||||
@@ -98,9 +45,9 @@ func TestExport(t *testing.T) {
|
|||||||
if err := e.Close(); err != nil {
|
if err := e.Close(); err != nil {
|
||||||
t.Errorf("Close: error = %v", err)
|
t.Errorf("Close: error = %v", err)
|
||||||
}
|
}
|
||||||
if got := digest.Sum(nil); !slices.Equal(got, tc.want) {
|
if got := digest.Sum(nil); !slices.Equal(got, want) {
|
||||||
t.Fatalf("Export() hash = %x, want %x",
|
t.Fatalf("Export() hash = %x, want %x",
|
||||||
got, tc.want)
|
got, want)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -132,7 +79,7 @@ func TestExport(t *testing.T) {
|
|||||||
|
|
||||||
func BenchmarkExport(b *testing.B) {
|
func BenchmarkExport(b *testing.B) {
|
||||||
buf := make([]byte, 8)
|
buf := make([]byte, 8)
|
||||||
for i := 0; i < b.N; i++ {
|
for b.Loop() {
|
||||||
e := New(
|
e := New(
|
||||||
Preset(PresetExt|PresetDenyNS|PresetDenyTTY|PresetDenyDevel|PresetLinux32,
|
Preset(PresetExt|PresetDenyNS|PresetDenyTTY|PresetDenyDevel|PresetLinux32,
|
||||||
AllowMultiarch|AllowCAN|AllowBluetooth),
|
AllowMultiarch|AllowCAN|AllowBluetooth),
|
||||||
|
|||||||
@@ -4,8 +4,14 @@
|
|||||||
# license that can be found in the LICENSE file.
|
# license that can be found in the LICENSE file.
|
||||||
|
|
||||||
use strict;
|
use strict;
|
||||||
|
use POSIX ();
|
||||||
|
|
||||||
my $command = "mksysnum_linux.pl ". join(' ', @ARGV);
|
my $command = "mksysnum_linux.pl ". join(' ', @ARGV);
|
||||||
|
my $uname_arch = (POSIX::uname)[4];
|
||||||
|
my %syscall_cutoff_arch = (
|
||||||
|
"x86_64" => 302,
|
||||||
|
"aarch64" => 281,
|
||||||
|
);
|
||||||
|
|
||||||
print <<EOF;
|
print <<EOF;
|
||||||
// $command
|
// $command
|
||||||
@@ -30,7 +36,7 @@ sub fmt {
|
|||||||
}
|
}
|
||||||
(my $name_upper = $name) =~ y/a-z/A-Z/;
|
(my $name_upper = $name) =~ y/a-z/A-Z/;
|
||||||
$num = $num + $offset;
|
$num = $num + $offset;
|
||||||
if($num > 302){ # not wired in Go standard library
|
if($num > $syscall_cutoff_arch{$uname_arch}){ # not wired in Go standard library
|
||||||
if($state < 0){
|
if($state < 0){
|
||||||
print " \"$name\": SYS_$name_upper,\n";
|
print " \"$name\": SYS_$name_upper,\n";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,9 @@ package seccomp
|
|||||||
#cgo linux pkg-config: --static libseccomp
|
#cgo linux pkg-config: --static libseccomp
|
||||||
|
|
||||||
#include <seccomp.h>
|
#include <seccomp.h>
|
||||||
#include <sys/personality.h>
|
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
const (
|
|
||||||
PER_LINUX = C.PER_LINUX
|
|
||||||
PER_LINUX32 = C.PER_LINUX32
|
|
||||||
)
|
|
||||||
|
|
||||||
var syscallNumExtra = map[string]int{
|
var syscallNumExtra = map[string]int{
|
||||||
"umount": SYS_UMOUNT,
|
"umount": SYS_UMOUNT,
|
||||||
"subpage_prot": SYS_SUBPAGE_PROT,
|
"subpage_prot": SYS_SUBPAGE_PROT,
|
||||||
|
|||||||
61
container/seccomp/syscall_extra_linux_arm64.go
Normal file
61
container/seccomp/syscall_extra_linux_arm64.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package seccomp
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo linux pkg-config: --static libseccomp
|
||||||
|
|
||||||
|
#include <seccomp.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const (
|
||||||
|
SYS_NEWFSTATAT = syscall.SYS_FSTATAT
|
||||||
|
)
|
||||||
|
|
||||||
|
var syscallNumExtra = map[string]int{
|
||||||
|
"uselib": SYS_USELIB,
|
||||||
|
"clock_adjtime64": SYS_CLOCK_ADJTIME64,
|
||||||
|
"clock_settime64": SYS_CLOCK_SETTIME64,
|
||||||
|
"umount": SYS_UMOUNT,
|
||||||
|
"chown": SYS_CHOWN,
|
||||||
|
"chown32": SYS_CHOWN32,
|
||||||
|
"fchown32": SYS_FCHOWN32,
|
||||||
|
"lchown": SYS_LCHOWN,
|
||||||
|
"lchown32": SYS_LCHOWN32,
|
||||||
|
"setgid32": SYS_SETGID32,
|
||||||
|
"setgroups32": SYS_SETGROUPS32,
|
||||||
|
"setregid32": SYS_SETREGID32,
|
||||||
|
"setresgid32": SYS_SETRESGID32,
|
||||||
|
"setresuid32": SYS_SETRESUID32,
|
||||||
|
"setreuid32": SYS_SETREUID32,
|
||||||
|
"setuid32": SYS_SETUID32,
|
||||||
|
"modify_ldt": SYS_MODIFY_LDT,
|
||||||
|
"subpage_prot": SYS_SUBPAGE_PROT,
|
||||||
|
"switch_endian": SYS_SWITCH_ENDIAN,
|
||||||
|
"vm86": SYS_VM86,
|
||||||
|
"vm86old": SYS_VM86OLD,
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
SYS_USELIB = C.__SNR_uselib
|
||||||
|
SYS_CLOCK_ADJTIME64 = C.__SNR_clock_adjtime64
|
||||||
|
SYS_CLOCK_SETTIME64 = C.__SNR_clock_settime64
|
||||||
|
SYS_UMOUNT = C.__SNR_umount
|
||||||
|
SYS_CHOWN = C.__SNR_chown
|
||||||
|
SYS_CHOWN32 = C.__SNR_chown32
|
||||||
|
SYS_FCHOWN32 = C.__SNR_fchown32
|
||||||
|
SYS_LCHOWN = C.__SNR_lchown
|
||||||
|
SYS_LCHOWN32 = C.__SNR_lchown32
|
||||||
|
SYS_SETGID32 = C.__SNR_setgid32
|
||||||
|
SYS_SETGROUPS32 = C.__SNR_setgroups32
|
||||||
|
SYS_SETREGID32 = C.__SNR_setregid32
|
||||||
|
SYS_SETRESGID32 = C.__SNR_setresgid32
|
||||||
|
SYS_SETRESUID32 = C.__SNR_setresuid32
|
||||||
|
SYS_SETREUID32 = C.__SNR_setreuid32
|
||||||
|
SYS_SETUID32 = C.__SNR_setuid32
|
||||||
|
SYS_MODIFY_LDT = C.__SNR_modify_ldt
|
||||||
|
SYS_SUBPAGE_PROT = C.__SNR_subpage_prot
|
||||||
|
SYS_SWITCH_ENDIAN = C.__SNR_switch_endian
|
||||||
|
SYS_VM86 = C.__SNR_vm86
|
||||||
|
SYS_VM86OLD = C.__SNR_vm86old
|
||||||
|
)
|
||||||
382
container/seccomp/syscall_linux_arm64.go
Normal file
382
container/seccomp/syscall_linux_arm64.go
Normal file
@@ -0,0 +1,382 @@
|
|||||||
|
// mksysnum_linux.pl /usr/include/asm/unistd_64.h
|
||||||
|
// Code generated by the command above; DO NOT EDIT.
|
||||||
|
|
||||||
|
package seccomp
|
||||||
|
|
||||||
|
import . "syscall"
|
||||||
|
|
||||||
|
var syscallNum = map[string]int{
|
||||||
|
"io_setup": SYS_IO_SETUP,
|
||||||
|
"io_destroy": SYS_IO_DESTROY,
|
||||||
|
"io_submit": SYS_IO_SUBMIT,
|
||||||
|
"io_cancel": SYS_IO_CANCEL,
|
||||||
|
"io_getevents": SYS_IO_GETEVENTS,
|
||||||
|
"setxattr": SYS_SETXATTR,
|
||||||
|
"lsetxattr": SYS_LSETXATTR,
|
||||||
|
"fsetxattr": SYS_FSETXATTR,
|
||||||
|
"getxattr": SYS_GETXATTR,
|
||||||
|
"lgetxattr": SYS_LGETXATTR,
|
||||||
|
"fgetxattr": SYS_FGETXATTR,
|
||||||
|
"listxattr": SYS_LISTXATTR,
|
||||||
|
"llistxattr": SYS_LLISTXATTR,
|
||||||
|
"flistxattr": SYS_FLISTXATTR,
|
||||||
|
"removexattr": SYS_REMOVEXATTR,
|
||||||
|
"lremovexattr": SYS_LREMOVEXATTR,
|
||||||
|
"fremovexattr": SYS_FREMOVEXATTR,
|
||||||
|
"getcwd": SYS_GETCWD,
|
||||||
|
"lookup_dcookie": SYS_LOOKUP_DCOOKIE,
|
||||||
|
"eventfd2": SYS_EVENTFD2,
|
||||||
|
"epoll_create1": SYS_EPOLL_CREATE1,
|
||||||
|
"epoll_ctl": SYS_EPOLL_CTL,
|
||||||
|
"epoll_pwait": SYS_EPOLL_PWAIT,
|
||||||
|
"dup": SYS_DUP,
|
||||||
|
"dup3": SYS_DUP3,
|
||||||
|
"fcntl": SYS_FCNTL,
|
||||||
|
"inotify_init1": SYS_INOTIFY_INIT1,
|
||||||
|
"inotify_add_watch": SYS_INOTIFY_ADD_WATCH,
|
||||||
|
"inotify_rm_watch": SYS_INOTIFY_RM_WATCH,
|
||||||
|
"ioctl": SYS_IOCTL,
|
||||||
|
"ioprio_set": SYS_IOPRIO_SET,
|
||||||
|
"ioprio_get": SYS_IOPRIO_GET,
|
||||||
|
"flock": SYS_FLOCK,
|
||||||
|
"mknodat": SYS_MKNODAT,
|
||||||
|
"mkdirat": SYS_MKDIRAT,
|
||||||
|
"unlinkat": SYS_UNLINKAT,
|
||||||
|
"symlinkat": SYS_SYMLINKAT,
|
||||||
|
"linkat": SYS_LINKAT,
|
||||||
|
"renameat": SYS_RENAMEAT,
|
||||||
|
"umount2": SYS_UMOUNT2,
|
||||||
|
"mount": SYS_MOUNT,
|
||||||
|
"pivot_root": SYS_PIVOT_ROOT,
|
||||||
|
"nfsservctl": SYS_NFSSERVCTL,
|
||||||
|
"statfs": SYS_STATFS,
|
||||||
|
"fstatfs": SYS_FSTATFS,
|
||||||
|
"truncate": SYS_TRUNCATE,
|
||||||
|
"ftruncate": SYS_FTRUNCATE,
|
||||||
|
"fallocate": SYS_FALLOCATE,
|
||||||
|
"faccessat": SYS_FACCESSAT,
|
||||||
|
"chdir": SYS_CHDIR,
|
||||||
|
"fchdir": SYS_FCHDIR,
|
||||||
|
"chroot": SYS_CHROOT,
|
||||||
|
"fchmod": SYS_FCHMOD,
|
||||||
|
"fchmodat": SYS_FCHMODAT,
|
||||||
|
"fchownat": SYS_FCHOWNAT,
|
||||||
|
"fchown": SYS_FCHOWN,
|
||||||
|
"openat": SYS_OPENAT,
|
||||||
|
"close": SYS_CLOSE,
|
||||||
|
"vhangup": SYS_VHANGUP,
|
||||||
|
"pipe2": SYS_PIPE2,
|
||||||
|
"quotactl": SYS_QUOTACTL,
|
||||||
|
"getdents64": SYS_GETDENTS64,
|
||||||
|
"lseek": SYS_LSEEK,
|
||||||
|
"read": SYS_READ,
|
||||||
|
"write": SYS_WRITE,
|
||||||
|
"readv": SYS_READV,
|
||||||
|
"writev": SYS_WRITEV,
|
||||||
|
"pread64": SYS_PREAD64,
|
||||||
|
"pwrite64": SYS_PWRITE64,
|
||||||
|
"preadv": SYS_PREADV,
|
||||||
|
"pwritev": SYS_PWRITEV,
|
||||||
|
"sendfile": SYS_SENDFILE,
|
||||||
|
"pselect6": SYS_PSELECT6,
|
||||||
|
"ppoll": SYS_PPOLL,
|
||||||
|
"signalfd4": SYS_SIGNALFD4,
|
||||||
|
"vmsplice": SYS_VMSPLICE,
|
||||||
|
"splice": SYS_SPLICE,
|
||||||
|
"tee": SYS_TEE,
|
||||||
|
"readlinkat": SYS_READLINKAT,
|
||||||
|
"newfstatat": SYS_NEWFSTATAT,
|
||||||
|
"fstat": SYS_FSTAT,
|
||||||
|
"sync": SYS_SYNC,
|
||||||
|
"fsync": SYS_FSYNC,
|
||||||
|
"fdatasync": SYS_FDATASYNC,
|
||||||
|
"sync_file_range": SYS_SYNC_FILE_RANGE,
|
||||||
|
"timerfd_create": SYS_TIMERFD_CREATE,
|
||||||
|
"timerfd_settime": SYS_TIMERFD_SETTIME,
|
||||||
|
"timerfd_gettime": SYS_TIMERFD_GETTIME,
|
||||||
|
"utimensat": SYS_UTIMENSAT,
|
||||||
|
"acct": SYS_ACCT,
|
||||||
|
"capget": SYS_CAPGET,
|
||||||
|
"capset": SYS_CAPSET,
|
||||||
|
"personality": SYS_PERSONALITY,
|
||||||
|
"exit": SYS_EXIT,
|
||||||
|
"exit_group": SYS_EXIT_GROUP,
|
||||||
|
"waitid": SYS_WAITID,
|
||||||
|
"set_tid_address": SYS_SET_TID_ADDRESS,
|
||||||
|
"unshare": SYS_UNSHARE,
|
||||||
|
"futex": SYS_FUTEX,
|
||||||
|
"set_robust_list": SYS_SET_ROBUST_LIST,
|
||||||
|
"get_robust_list": SYS_GET_ROBUST_LIST,
|
||||||
|
"nanosleep": SYS_NANOSLEEP,
|
||||||
|
"getitimer": SYS_GETITIMER,
|
||||||
|
"setitimer": SYS_SETITIMER,
|
||||||
|
"kexec_load": SYS_KEXEC_LOAD,
|
||||||
|
"init_module": SYS_INIT_MODULE,
|
||||||
|
"delete_module": SYS_DELETE_MODULE,
|
||||||
|
"timer_create": SYS_TIMER_CREATE,
|
||||||
|
"timer_gettime": SYS_TIMER_GETTIME,
|
||||||
|
"timer_getoverrun": SYS_TIMER_GETOVERRUN,
|
||||||
|
"timer_settime": SYS_TIMER_SETTIME,
|
||||||
|
"timer_delete": SYS_TIMER_DELETE,
|
||||||
|
"clock_settime": SYS_CLOCK_SETTIME,
|
||||||
|
"clock_gettime": SYS_CLOCK_GETTIME,
|
||||||
|
"clock_getres": SYS_CLOCK_GETRES,
|
||||||
|
"clock_nanosleep": SYS_CLOCK_NANOSLEEP,
|
||||||
|
"syslog": SYS_SYSLOG,
|
||||||
|
"ptrace": SYS_PTRACE,
|
||||||
|
"sched_setparam": SYS_SCHED_SETPARAM,
|
||||||
|
"sched_setscheduler": SYS_SCHED_SETSCHEDULER,
|
||||||
|
"sched_getscheduler": SYS_SCHED_GETSCHEDULER,
|
||||||
|
"sched_getparam": SYS_SCHED_GETPARAM,
|
||||||
|
"sched_setaffinity": SYS_SCHED_SETAFFINITY,
|
||||||
|
"sched_getaffinity": SYS_SCHED_GETAFFINITY,
|
||||||
|
"sched_yield": SYS_SCHED_YIELD,
|
||||||
|
"sched_get_priority_max": SYS_SCHED_GET_PRIORITY_MAX,
|
||||||
|
"sched_get_priority_min": SYS_SCHED_GET_PRIORITY_MIN,
|
||||||
|
"sched_rr_get_interval": SYS_SCHED_RR_GET_INTERVAL,
|
||||||
|
"restart_syscall": SYS_RESTART_SYSCALL,
|
||||||
|
"kill": SYS_KILL,
|
||||||
|
"tkill": SYS_TKILL,
|
||||||
|
"tgkill": SYS_TGKILL,
|
||||||
|
"sigaltstack": SYS_SIGALTSTACK,
|
||||||
|
"rt_sigsuspend": SYS_RT_SIGSUSPEND,
|
||||||
|
"rt_sigaction": SYS_RT_SIGACTION,
|
||||||
|
"rt_sigprocmask": SYS_RT_SIGPROCMASK,
|
||||||
|
"rt_sigpending": SYS_RT_SIGPENDING,
|
||||||
|
"rt_sigtimedwait": SYS_RT_SIGTIMEDWAIT,
|
||||||
|
"rt_sigqueueinfo": SYS_RT_SIGQUEUEINFO,
|
||||||
|
"rt_sigreturn": SYS_RT_SIGRETURN,
|
||||||
|
"setpriority": SYS_SETPRIORITY,
|
||||||
|
"getpriority": SYS_GETPRIORITY,
|
||||||
|
"reboot": SYS_REBOOT,
|
||||||
|
"setregid": SYS_SETREGID,
|
||||||
|
"setgid": SYS_SETGID,
|
||||||
|
"setreuid": SYS_SETREUID,
|
||||||
|
"setuid": SYS_SETUID,
|
||||||
|
"setresuid": SYS_SETRESUID,
|
||||||
|
"getresuid": SYS_GETRESUID,
|
||||||
|
"setresgid": SYS_SETRESGID,
|
||||||
|
"getresgid": SYS_GETRESGID,
|
||||||
|
"setfsuid": SYS_SETFSUID,
|
||||||
|
"setfsgid": SYS_SETFSGID,
|
||||||
|
"times": SYS_TIMES,
|
||||||
|
"setpgid": SYS_SETPGID,
|
||||||
|
"getpgid": SYS_GETPGID,
|
||||||
|
"getsid": SYS_GETSID,
|
||||||
|
"setsid": SYS_SETSID,
|
||||||
|
"getgroups": SYS_GETGROUPS,
|
||||||
|
"setgroups": SYS_SETGROUPS,
|
||||||
|
"uname": SYS_UNAME,
|
||||||
|
"sethostname": SYS_SETHOSTNAME,
|
||||||
|
"setdomainname": SYS_SETDOMAINNAME,
|
||||||
|
"getrlimit": SYS_GETRLIMIT,
|
||||||
|
"setrlimit": SYS_SETRLIMIT,
|
||||||
|
"getrusage": SYS_GETRUSAGE,
|
||||||
|
"umask": SYS_UMASK,
|
||||||
|
"prctl": SYS_PRCTL,
|
||||||
|
"getcpu": SYS_GETCPU,
|
||||||
|
"gettimeofday": SYS_GETTIMEOFDAY,
|
||||||
|
"settimeofday": SYS_SETTIMEOFDAY,
|
||||||
|
"adjtimex": SYS_ADJTIMEX,
|
||||||
|
"getpid": SYS_GETPID,
|
||||||
|
"getppid": SYS_GETPPID,
|
||||||
|
"getuid": SYS_GETUID,
|
||||||
|
"geteuid": SYS_GETEUID,
|
||||||
|
"getgid": SYS_GETGID,
|
||||||
|
"getegid": SYS_GETEGID,
|
||||||
|
"gettid": SYS_GETTID,
|
||||||
|
"sysinfo": SYS_SYSINFO,
|
||||||
|
"mq_open": SYS_MQ_OPEN,
|
||||||
|
"mq_unlink": SYS_MQ_UNLINK,
|
||||||
|
"mq_timedsend": SYS_MQ_TIMEDSEND,
|
||||||
|
"mq_timedreceive": SYS_MQ_TIMEDRECEIVE,
|
||||||
|
"mq_notify": SYS_MQ_NOTIFY,
|
||||||
|
"mq_getsetattr": SYS_MQ_GETSETATTR,
|
||||||
|
"msgget": SYS_MSGGET,
|
||||||
|
"msgctl": SYS_MSGCTL,
|
||||||
|
"msgrcv": SYS_MSGRCV,
|
||||||
|
"msgsnd": SYS_MSGSND,
|
||||||
|
"semget": SYS_SEMGET,
|
||||||
|
"semctl": SYS_SEMCTL,
|
||||||
|
"semtimedop": SYS_SEMTIMEDOP,
|
||||||
|
"semop": SYS_SEMOP,
|
||||||
|
"shmget": SYS_SHMGET,
|
||||||
|
"shmctl": SYS_SHMCTL,
|
||||||
|
"shmat": SYS_SHMAT,
|
||||||
|
"shmdt": SYS_SHMDT,
|
||||||
|
"socket": SYS_SOCKET,
|
||||||
|
"socketpair": SYS_SOCKETPAIR,
|
||||||
|
"bind": SYS_BIND,
|
||||||
|
"listen": SYS_LISTEN,
|
||||||
|
"accept": SYS_ACCEPT,
|
||||||
|
"connect": SYS_CONNECT,
|
||||||
|
"getsockname": SYS_GETSOCKNAME,
|
||||||
|
"getpeername": SYS_GETPEERNAME,
|
||||||
|
"sendto": SYS_SENDTO,
|
||||||
|
"recvfrom": SYS_RECVFROM,
|
||||||
|
"setsockopt": SYS_SETSOCKOPT,
|
||||||
|
"getsockopt": SYS_GETSOCKOPT,
|
||||||
|
"shutdown": SYS_SHUTDOWN,
|
||||||
|
"sendmsg": SYS_SENDMSG,
|
||||||
|
"recvmsg": SYS_RECVMSG,
|
||||||
|
"readahead": SYS_READAHEAD,
|
||||||
|
"brk": SYS_BRK,
|
||||||
|
"munmap": SYS_MUNMAP,
|
||||||
|
"mremap": SYS_MREMAP,
|
||||||
|
"add_key": SYS_ADD_KEY,
|
||||||
|
"request_key": SYS_REQUEST_KEY,
|
||||||
|
"keyctl": SYS_KEYCTL,
|
||||||
|
"clone": SYS_CLONE,
|
||||||
|
"execve": SYS_EXECVE,
|
||||||
|
"mmap": SYS_MMAP,
|
||||||
|
"fadvise64": SYS_FADVISE64,
|
||||||
|
"swapon": SYS_SWAPON,
|
||||||
|
"swapoff": SYS_SWAPOFF,
|
||||||
|
"mprotect": SYS_MPROTECT,
|
||||||
|
"msync": SYS_MSYNC,
|
||||||
|
"mlock": SYS_MLOCK,
|
||||||
|
"munlock": SYS_MUNLOCK,
|
||||||
|
"mlockall": SYS_MLOCKALL,
|
||||||
|
"munlockall": SYS_MUNLOCKALL,
|
||||||
|
"mincore": SYS_MINCORE,
|
||||||
|
"madvise": SYS_MADVISE,
|
||||||
|
"remap_file_pages": SYS_REMAP_FILE_PAGES,
|
||||||
|
"mbind": SYS_MBIND,
|
||||||
|
"get_mempolicy": SYS_GET_MEMPOLICY,
|
||||||
|
"set_mempolicy": SYS_SET_MEMPOLICY,
|
||||||
|
"migrate_pages": SYS_MIGRATE_PAGES,
|
||||||
|
"move_pages": SYS_MOVE_PAGES,
|
||||||
|
"rt_tgsigqueueinfo": SYS_RT_TGSIGQUEUEINFO,
|
||||||
|
"perf_event_open": SYS_PERF_EVENT_OPEN,
|
||||||
|
"accept4": SYS_ACCEPT4,
|
||||||
|
"recvmmsg": SYS_RECVMMSG,
|
||||||
|
"wait4": SYS_WAIT4,
|
||||||
|
"prlimit64": SYS_PRLIMIT64,
|
||||||
|
"fanotify_init": SYS_FANOTIFY_INIT,
|
||||||
|
"fanotify_mark": SYS_FANOTIFY_MARK,
|
||||||
|
"name_to_handle_at": SYS_NAME_TO_HANDLE_AT,
|
||||||
|
"open_by_handle_at": SYS_OPEN_BY_HANDLE_AT,
|
||||||
|
"clock_adjtime": SYS_CLOCK_ADJTIME,
|
||||||
|
"syncfs": SYS_SYNCFS,
|
||||||
|
"setns": SYS_SETNS,
|
||||||
|
"sendmmsg": SYS_SENDMMSG,
|
||||||
|
"process_vm_readv": SYS_PROCESS_VM_READV,
|
||||||
|
"process_vm_writev": SYS_PROCESS_VM_WRITEV,
|
||||||
|
"kcmp": SYS_KCMP,
|
||||||
|
"finit_module": SYS_FINIT_MODULE,
|
||||||
|
"sched_setattr": SYS_SCHED_SETATTR,
|
||||||
|
"sched_getattr": SYS_SCHED_GETATTR,
|
||||||
|
"renameat2": SYS_RENAMEAT2,
|
||||||
|
"seccomp": SYS_SECCOMP,
|
||||||
|
"getrandom": SYS_GETRANDOM,
|
||||||
|
"memfd_create": SYS_MEMFD_CREATE,
|
||||||
|
"bpf": SYS_BPF,
|
||||||
|
"execveat": SYS_EXECVEAT,
|
||||||
|
"userfaultfd": SYS_USERFAULTFD,
|
||||||
|
"membarrier": SYS_MEMBARRIER,
|
||||||
|
"mlock2": SYS_MLOCK2,
|
||||||
|
"copy_file_range": SYS_COPY_FILE_RANGE,
|
||||||
|
"preadv2": SYS_PREADV2,
|
||||||
|
"pwritev2": SYS_PWRITEV2,
|
||||||
|
"pkey_mprotect": SYS_PKEY_MPROTECT,
|
||||||
|
"pkey_alloc": SYS_PKEY_ALLOC,
|
||||||
|
"pkey_free": SYS_PKEY_FREE,
|
||||||
|
"statx": SYS_STATX,
|
||||||
|
"io_pgetevents": SYS_IO_PGETEVENTS,
|
||||||
|
"rseq": SYS_RSEQ,
|
||||||
|
"kexec_file_load": SYS_KEXEC_FILE_LOAD,
|
||||||
|
"pidfd_send_signal": SYS_PIDFD_SEND_SIGNAL,
|
||||||
|
"io_uring_setup": SYS_IO_URING_SETUP,
|
||||||
|
"io_uring_enter": SYS_IO_URING_ENTER,
|
||||||
|
"io_uring_register": SYS_IO_URING_REGISTER,
|
||||||
|
"open_tree": SYS_OPEN_TREE,
|
||||||
|
"move_mount": SYS_MOVE_MOUNT,
|
||||||
|
"fsopen": SYS_FSOPEN,
|
||||||
|
"fsconfig": SYS_FSCONFIG,
|
||||||
|
"fsmount": SYS_FSMOUNT,
|
||||||
|
"fspick": SYS_FSPICK,
|
||||||
|
"pidfd_open": SYS_PIDFD_OPEN,
|
||||||
|
"clone3": SYS_CLONE3,
|
||||||
|
"close_range": SYS_CLOSE_RANGE,
|
||||||
|
"openat2": SYS_OPENAT2,
|
||||||
|
"pidfd_getfd": SYS_PIDFD_GETFD,
|
||||||
|
"faccessat2": SYS_FACCESSAT2,
|
||||||
|
"process_madvise": SYS_PROCESS_MADVISE,
|
||||||
|
"epoll_pwait2": SYS_EPOLL_PWAIT2,
|
||||||
|
"mount_setattr": SYS_MOUNT_SETATTR,
|
||||||
|
"quotactl_fd": SYS_QUOTACTL_FD,
|
||||||
|
"landlock_create_ruleset": SYS_LANDLOCK_CREATE_RULESET,
|
||||||
|
"landlock_add_rule": SYS_LANDLOCK_ADD_RULE,
|
||||||
|
"landlock_restrict_self": SYS_LANDLOCK_RESTRICT_SELF,
|
||||||
|
"memfd_secret": SYS_MEMFD_SECRET,
|
||||||
|
"process_mrelease": SYS_PROCESS_MRELEASE,
|
||||||
|
"futex_waitv": SYS_FUTEX_WAITV,
|
||||||
|
"set_mempolicy_home_node": SYS_SET_MEMPOLICY_HOME_NODE,
|
||||||
|
"cachestat": SYS_CACHESTAT,
|
||||||
|
"fchmodat2": SYS_FCHMODAT2,
|
||||||
|
"map_shadow_stack": SYS_MAP_SHADOW_STACK,
|
||||||
|
"futex_wake": SYS_FUTEX_WAKE,
|
||||||
|
"futex_wait": SYS_FUTEX_WAIT,
|
||||||
|
"futex_requeue": SYS_FUTEX_REQUEUE,
|
||||||
|
"statmount": SYS_STATMOUNT,
|
||||||
|
"listmount": SYS_LISTMOUNT,
|
||||||
|
"lsm_get_self_attr": SYS_LSM_GET_SELF_ATTR,
|
||||||
|
"lsm_set_self_attr": SYS_LSM_SET_SELF_ATTR,
|
||||||
|
"lsm_list_modules": SYS_LSM_LIST_MODULES,
|
||||||
|
"mseal": SYS_MSEAL,
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
SYS_USERFAULTFD = 282
|
||||||
|
SYS_MEMBARRIER = 283
|
||||||
|
SYS_MLOCK2 = 284
|
||||||
|
SYS_COPY_FILE_RANGE = 285
|
||||||
|
SYS_PREADV2 = 286
|
||||||
|
SYS_PWRITEV2 = 287
|
||||||
|
SYS_PKEY_MPROTECT = 288
|
||||||
|
SYS_PKEY_ALLOC = 289
|
||||||
|
SYS_PKEY_FREE = 290
|
||||||
|
SYS_STATX = 291
|
||||||
|
SYS_IO_PGETEVENTS = 292
|
||||||
|
SYS_RSEQ = 293
|
||||||
|
SYS_KEXEC_FILE_LOAD = 294
|
||||||
|
SYS_PIDFD_SEND_SIGNAL = 424
|
||||||
|
SYS_IO_URING_SETUP = 425
|
||||||
|
SYS_IO_URING_ENTER = 426
|
||||||
|
SYS_IO_URING_REGISTER = 427
|
||||||
|
SYS_OPEN_TREE = 428
|
||||||
|
SYS_MOVE_MOUNT = 429
|
||||||
|
SYS_FSOPEN = 430
|
||||||
|
SYS_FSCONFIG = 431
|
||||||
|
SYS_FSMOUNT = 432
|
||||||
|
SYS_FSPICK = 433
|
||||||
|
SYS_PIDFD_OPEN = 434
|
||||||
|
SYS_CLONE3 = 435
|
||||||
|
SYS_CLOSE_RANGE = 436
|
||||||
|
SYS_OPENAT2 = 437
|
||||||
|
SYS_PIDFD_GETFD = 438
|
||||||
|
SYS_FACCESSAT2 = 439
|
||||||
|
SYS_PROCESS_MADVISE = 440
|
||||||
|
SYS_EPOLL_PWAIT2 = 441
|
||||||
|
SYS_MOUNT_SETATTR = 442
|
||||||
|
SYS_QUOTACTL_FD = 443
|
||||||
|
SYS_LANDLOCK_CREATE_RULESET = 444
|
||||||
|
SYS_LANDLOCK_ADD_RULE = 445
|
||||||
|
SYS_LANDLOCK_RESTRICT_SELF = 446
|
||||||
|
SYS_MEMFD_SECRET = 447
|
||||||
|
SYS_PROCESS_MRELEASE = 448
|
||||||
|
SYS_FUTEX_WAITV = 449
|
||||||
|
SYS_SET_MEMPOLICY_HOME_NODE = 450
|
||||||
|
SYS_CACHESTAT = 451
|
||||||
|
SYS_FCHMODAT2 = 452
|
||||||
|
SYS_MAP_SHADOW_STACK = 453
|
||||||
|
SYS_FUTEX_WAKE = 454
|
||||||
|
SYS_FUTEX_WAIT = 455
|
||||||
|
SYS_FUTEX_REQUEUE = 456
|
||||||
|
SYS_STATMOUNT = 457
|
||||||
|
SYS_LISTMOUNT = 458
|
||||||
|
SYS_LSM_GET_SELF_ATTR = 459
|
||||||
|
SYS_LSM_SET_SELF_ATTR = 460
|
||||||
|
SYS_LSM_LIST_MODULES = 461
|
||||||
|
SYS_MSEAL = 462
|
||||||
|
)
|
||||||
@@ -2,16 +2,6 @@ package container
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
O_PATH = 0x200000
|
|
||||||
|
|
||||||
PR_SET_NO_NEW_PRIVS = 0x26
|
|
||||||
|
|
||||||
CAP_SYS_ADMIN = 0x15
|
|
||||||
CAP_SETPCAP = 0x8
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -28,42 +18,6 @@ func SetDumpable(dumpable uintptr) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
_LINUX_CAPABILITY_VERSION_3 = 0x20080522
|
|
||||||
|
|
||||||
PR_CAP_AMBIENT = 0x2f
|
|
||||||
PR_CAP_AMBIENT_RAISE = 0x2
|
|
||||||
PR_CAP_AMBIENT_CLEAR_ALL = 0x4
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
capHeader struct {
|
|
||||||
version uint32
|
|
||||||
pid int32
|
|
||||||
}
|
|
||||||
|
|
||||||
capData struct {
|
|
||||||
effective uint32
|
|
||||||
permitted uint32
|
|
||||||
inheritable uint32
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// See CAP_TO_INDEX in linux/capability.h:
|
|
||||||
func capToIndex(cap uintptr) uintptr { return cap >> 5 }
|
|
||||||
|
|
||||||
// See CAP_TO_MASK in linux/capability.h:
|
|
||||||
func capToMask(cap uintptr) uint32 { return 1 << uint(cap&31) }
|
|
||||||
|
|
||||||
func capset(hdrp *capHeader, datap *[2]capData) error {
|
|
||||||
if _, _, errno := syscall.Syscall(syscall.SYS_CAPSET,
|
|
||||||
uintptr(unsafe.Pointer(hdrp)),
|
|
||||||
uintptr(unsafe.Pointer(&datap[0])), 0); errno != 0 {
|
|
||||||
return errno
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IgnoringEINTR makes a function call and repeats it if it returns an
|
// IgnoringEINTR makes a function call and repeats it if it returns an
|
||||||
// EINTR error. This appears to be required even though we install all
|
// EINTR error. This appears to be required even though we install all
|
||||||
// signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846.
|
// signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846.
|
||||||
|
|||||||
7
container/syscall_amd64.go
Normal file
7
container/syscall_amd64.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package container
|
||||||
|
|
||||||
|
const (
|
||||||
|
O_PATH = 0x200000
|
||||||
|
|
||||||
|
PR_SET_NO_NEW_PRIVS = 0x26
|
||||||
|
)
|
||||||
12
flake.lock
generated
12
flake.lock
generated
@@ -7,11 +7,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1748665073,
|
"lastModified": 1753479839,
|
||||||
"narHash": "sha256-RMhjnPKWtCoIIHiuR9QKD7xfsKb3agxzMfJY8V9MOew=",
|
"narHash": "sha256-E/rPVh7vyPMJUFl2NAew+zibNGfVbANr8BP8nLRbLkQ=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "282e1e029cb6ab4811114fc85110613d72771dea",
|
"rev": "0b9bf983db4d064764084cd6748efb1ab8297d1e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -23,11 +23,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1749024892,
|
"lastModified": 1753345091,
|
||||||
"narHash": "sha256-OGcDEz60TXQC+gVz5sdtgGJdKVYr6rwdzQKuZAJQpCA=",
|
"narHash": "sha256-CdX2Rtvp5I8HGu9swBmYuq+ILwRxpXdJwlpg8jvN4tU=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "8f1b52b04f2cb6e5ead50bd28d76528a2f0380ef",
|
"rev": "3ff0e34b1383648053bba8ed03f201d3466f90c9",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
28
flake.nix
28
flake.nix
@@ -185,17 +185,23 @@
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
generateSyscallTable = pkgs.mkShell {
|
generateSyscallTable =
|
||||||
# this should be made cross-platform via nix
|
let
|
||||||
shellHook = "exec ${pkgs.writeShellScript "generate-syscall-table" ''
|
GOARCH = {
|
||||||
set -e
|
x86_64-linux = "amd64";
|
||||||
${pkgs.perl}/bin/perl \
|
aarch64-linux = "arm64";
|
||||||
sandbox/seccomp/mksysnum_linux.pl \
|
};
|
||||||
${pkgs.linuxHeaders}/include/asm/unistd_64.h | \
|
in
|
||||||
${pkgs.go}/bin/gofmt > \
|
pkgs.mkShell {
|
||||||
sandbox/seccomp/syscall_linux_amd64.go
|
shellHook = "exec ${pkgs.writeShellScript "generate-syscall-table" ''
|
||||||
''}";
|
set -e
|
||||||
};
|
${pkgs.perl}/bin/perl \
|
||||||
|
container/seccomp/mksysnum_linux.pl \
|
||||||
|
${pkgs.linuxHeaders}/include/asm/unistd_64.h | \
|
||||||
|
${pkgs.go}/bin/gofmt > \
|
||||||
|
container/seccomp/syscall_linux_${GOARCH.${system}}.go
|
||||||
|
''}";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
"hakurei.app/helper"
|
"hakurei.app/helper"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCmd(t *testing.T) {
|
func TestCmd(t *testing.T) {
|
||||||
t.Run("start non-existent helper path", func(t *testing.T) {
|
t.Run("start non-existent helper path", func(t *testing.T) {
|
||||||
h := helper.NewDirect(t.Context(), "/proc/nonexistent", argsWt, false, argF, nil, nil)
|
h := helper.NewDirect(t.Context(), container.Nonexistent, argsWt, false, argF, nil, nil)
|
||||||
|
|
||||||
if err := h.Start(); !errors.Is(err, os.ErrNotExist) {
|
if err := h.Start(); !errors.Is(err, os.ErrNotExist) {
|
||||||
t.Errorf("Start: error = %v, wantErr %v",
|
t.Errorf("Start: error = %v, wantErr %v",
|
||||||
|
|||||||
@@ -4,20 +4,17 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/helper"
|
"hakurei.app/helper"
|
||||||
"hakurei.app/internal"
|
|
||||||
"hakurei.app/internal/hlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestContainer(t *testing.T) {
|
func TestContainer(t *testing.T) {
|
||||||
t.Run("start empty container", func(t *testing.T) {
|
t.Run("start empty container", func(t *testing.T) {
|
||||||
h := helper.New(t.Context(), "/nonexistent", argsWt, false, argF, nil, nil)
|
h := helper.New(t.Context(), container.Nonexistent, argsWt, false, argF, nil, nil)
|
||||||
|
|
||||||
wantErr := "sandbox: starting an empty container"
|
wantErr := "container: starting an empty container"
|
||||||
if err := h.Start(); err == nil || err.Error() != wantErr {
|
if err := h.Start(); err == nil || err.Error() != wantErr {
|
||||||
t.Errorf("Start: error = %v, wantErr %q",
|
t.Errorf("Start: error = %v, wantErr %q",
|
||||||
err, wantErr)
|
err, wantErr)
|
||||||
@@ -36,20 +33,8 @@ func TestContainer(t *testing.T) {
|
|||||||
testHelper(t, func(ctx context.Context, setOutput func(stdoutP, stderrP *io.Writer), stat bool) helper.Helper {
|
testHelper(t, func(ctx context.Context, setOutput func(stdoutP, stderrP *io.Writer), stat bool) helper.Helper {
|
||||||
return helper.New(ctx, os.Args[0], argsWt, stat, argF, func(z *container.Container) {
|
return helper.New(ctx, os.Args[0], argsWt, stat, argF, func(z *container.Container) {
|
||||||
setOutput(&z.Stdout, &z.Stderr)
|
setOutput(&z.Stdout, &z.Stderr)
|
||||||
z.CommandContext = func(ctx context.Context) (cmd *exec.Cmd) {
|
|
||||||
return exec.CommandContext(ctx, os.Args[0], "-test.v",
|
|
||||||
"-test.run=TestHelperInit", "--", "init")
|
|
||||||
}
|
|
||||||
z.Bind("/", "/", 0).Proc("/proc").Dev("/dev")
|
z.Bind("/", "/", 0).Proc("/proc").Dev("/dev")
|
||||||
}, nil)
|
}, nil)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHelperInit(t *testing.T) {
|
|
||||||
if len(os.Args) != 5 || os.Args[4] != "init" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container.SetOutput(hlog.Output{})
|
|
||||||
container.Init(hlog.Prepare, func(bool) { internal.InstallOutput(false) })
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ func argF(argsFd, statFd int) []string {
|
|||||||
|
|
||||||
func argFChecked(argsFd, statFd int) (args []string) {
|
func argFChecked(argsFd, statFd int) (args []string) {
|
||||||
args = make([]string, 0, 6)
|
args = make([]string, 0, 6)
|
||||||
args = append(args, "-test.run=TestHelperStub", "--")
|
|
||||||
if argsFd > -1 {
|
if argsFd > -1 {
|
||||||
args = append(args, "--args", strconv.Itoa(argsFd))
|
args = append(args, "--args", strconv.Itoa(argsFd))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func InternalHelperStub() {
|
|||||||
sp = v
|
sp = v
|
||||||
}
|
}
|
||||||
|
|
||||||
genericStub(flagRestoreFiles(3, ap, sp))
|
genericStub(flagRestoreFiles(1, ap, sp))
|
||||||
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
package helper_test
|
package helper_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
"hakurei.app/helper"
|
"hakurei.app/helper"
|
||||||
|
"hakurei.app/internal"
|
||||||
|
"hakurei.app/internal/hlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHelperStub(t *testing.T) { helper.InternalHelperStub() }
|
func TestMain(m *testing.M) {
|
||||||
|
container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput)
|
||||||
|
helper.InternalHelperStub()
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package hst
|
package hst
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -10,6 +12,10 @@ type (
|
|||||||
// container hostname
|
// container hostname
|
||||||
Hostname string `json:"hostname,omitempty"`
|
Hostname string `json:"hostname,omitempty"`
|
||||||
|
|
||||||
|
// duration to wait for after interrupting a container's initial process in nanoseconds;
|
||||||
|
// a negative value causes the container to be terminated immediately on cancellation
|
||||||
|
WaitDelay time.Duration `json:"wait_delay,omitempty"`
|
||||||
|
|
||||||
// extra seccomp flags
|
// extra seccomp flags
|
||||||
SeccompFlags seccomp.ExportFlag `json:"seccomp_flags"`
|
SeccompFlags seccomp.ExportFlag `json:"seccomp_flags"`
|
||||||
// extra seccomp presets
|
// extra seccomp presets
|
||||||
|
|||||||
@@ -62,8 +62,10 @@ func Template() *Config {
|
|||||||
Userns: true,
|
Userns: true,
|
||||||
Net: true,
|
Net: true,
|
||||||
Device: true,
|
Device: true,
|
||||||
|
WaitDelay: -1,
|
||||||
SeccompFlags: seccomp.AllowMultiarch,
|
SeccompFlags: seccomp.AllowMultiarch,
|
||||||
SeccompPresets: seccomp.PresetExt,
|
SeccompPresets: seccomp.PresetExt,
|
||||||
|
SeccompCompat: true,
|
||||||
Tty: true,
|
Tty: true,
|
||||||
Multiarch: true,
|
Multiarch: true,
|
||||||
MapRealUID: true,
|
MapRealUID: true,
|
||||||
|
|||||||
@@ -80,8 +80,10 @@ func TestTemplate(t *testing.T) {
|
|||||||
],
|
],
|
||||||
"container": {
|
"container": {
|
||||||
"hostname": "localhost",
|
"hostname": "localhost",
|
||||||
|
"wait_delay": -1,
|
||||||
"seccomp_flags": 1,
|
"seccomp_flags": 1,
|
||||||
"seccomp_presets": 1,
|
"seccomp_presets": 1,
|
||||||
|
"seccomp_compat": true,
|
||||||
"devel": true,
|
"devel": true,
|
||||||
"userns": true,
|
"userns": true,
|
||||||
"net": true,
|
"net": true,
|
||||||
|
|||||||
@@ -2,15 +2,19 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal/app/state"
|
||||||
|
"hakurei.app/internal/sys"
|
||||||
)
|
)
|
||||||
|
|
||||||
type App interface {
|
type App interface {
|
||||||
// ID returns a copy of [ID] held by App.
|
// ID returns a copy of [ID] held by App.
|
||||||
ID() ID
|
ID() state.ID
|
||||||
|
|
||||||
// Seal determines the outcome of config as a [SealedApp].
|
// Seal determines the outcome of config as a [SealedApp].
|
||||||
// The value of config might be overwritten and must not be used again.
|
// The value of config might be overwritten and must not be used again.
|
||||||
@@ -47,3 +51,11 @@ func (rs *RunState) SetStart() {
|
|||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
rs.Time = &now
|
rs.Time = &now
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MustNew(ctx context.Context, os sys.State) App {
|
||||||
|
a, err := New(ctx, os)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("cannot create app: %v", err)
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package setuid
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
. "hakurei.app/cmd/hakurei/internal/app"
|
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/hlog"
|
||||||
"hakurei.app/internal/sys"
|
"hakurei.app/internal/sys"
|
||||||
)
|
)
|
||||||
@@ -16,15 +16,15 @@ func New(ctx context.Context, os sys.State) (App, error) {
|
|||||||
a.sys = os
|
a.sys = os
|
||||||
a.ctx = ctx
|
a.ctx = ctx
|
||||||
|
|
||||||
id := new(ID)
|
id := new(state.ID)
|
||||||
err := NewAppID(id)
|
err := state.NewAppID(id)
|
||||||
a.id = newID(id)
|
a.id = newID(id)
|
||||||
|
|
||||||
return a, err
|
return a, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type app struct {
|
type app struct {
|
||||||
id *stringPair[ID]
|
id *stringPair[state.ID]
|
||||||
sys sys.State
|
sys sys.State
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ type app struct {
|
|||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *app) ID() ID { a.mu.RLock(); defer a.mu.RUnlock(); return a.id.unwrap() }
|
func (a *app) ID() state.ID { a.mu.RLock(); defer a.mu.RUnlock(); return a.id.unwrap() }
|
||||||
|
|
||||||
func (a *app) String() string {
|
func (a *app) String() string {
|
||||||
if a == nil {
|
if a == nil {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package setuid_test
|
package app_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -7,10 +7,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/cmd/hakurei/internal/app"
|
|
||||||
"hakurei.app/cmd/hakurei/internal/app/internal/setuid"
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal/app"
|
||||||
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/sys"
|
"hakurei.app/internal/sys"
|
||||||
"hakurei.app/system"
|
"hakurei.app/system"
|
||||||
)
|
)
|
||||||
@@ -19,7 +19,7 @@ type sealTestCase struct {
|
|||||||
name string
|
name string
|
||||||
os sys.State
|
os sys.State
|
||||||
config *hst.Config
|
config *hst.Config
|
||||||
id app.ID
|
id state.ID
|
||||||
wantSys *system.I
|
wantSys *system.I
|
||||||
wantContainer *container.Params
|
wantContainer *container.Params
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ func TestApp(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
a := setuid.NewWithID(tc.id, tc.os)
|
a := app.NewWithID(tc.id, tc.os)
|
||||||
var (
|
var (
|
||||||
gotSys *system.I
|
gotSys *system.I
|
||||||
gotContainer *container.Params
|
gotContainer *container.Params
|
||||||
@@ -39,7 +39,7 @@ func TestApp(t *testing.T) {
|
|||||||
t.Errorf("Seal: error = %v", err)
|
t.Errorf("Seal: error = %v", err)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
gotSys, gotContainer = setuid.AppIParams(a, sa)
|
gotSys, gotContainer = app.AppIParams(a, sa)
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
return
|
return
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package setuid_test
|
package app_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"hakurei.app/cmd/hakurei/internal/app"
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/system"
|
"hakurei.app/system"
|
||||||
"hakurei.app/system/acl"
|
"hakurei.app/system/acl"
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
@@ -52,7 +52,7 @@ var testCasesNixos = []sealTestCase{
|
|||||||
Data: "/var/lib/persist/module/hakurei/0/1",
|
Data: "/var/lib/persist/module/hakurei/0/1",
|
||||||
Identity: 1, Groups: []string{},
|
Identity: 1, Groups: []string{},
|
||||||
},
|
},
|
||||||
app.ID{
|
state.ID{
|
||||||
0x8e, 0x2c, 0x76, 0xb0,
|
0x8e, 0x2c, 0x76, 0xb0,
|
||||||
0x66, 0xda, 0xbe, 0x57,
|
0x66, 0xda, 0xbe, 0x57,
|
||||||
0x4c, 0xf0, 0x73, 0xbd,
|
0x4c, 0xf0, 0x73, 0xbd,
|
||||||
@@ -144,6 +144,7 @@ var testCasesNixos = []sealTestCase{
|
|||||||
Tmpfs("/var/run/nscd", 8192, 0755),
|
Tmpfs("/var/run/nscd", 8192, 0755),
|
||||||
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyTTY | seccomp.PresetDenyDevel,
|
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyTTY | seccomp.PresetDenyDevel,
|
||||||
HostNet: true,
|
HostNet: true,
|
||||||
|
ForwardCancel: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package setuid_test
|
package app_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"hakurei.app/cmd/hakurei/internal/app"
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/system"
|
"hakurei.app/system"
|
||||||
"hakurei.app/system/acl"
|
"hakurei.app/system/acl"
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
@@ -16,7 +16,7 @@ var testCasesPd = []sealTestCase{
|
|||||||
{
|
{
|
||||||
"nixos permissive defaults no enablements", new(stubNixOS),
|
"nixos permissive defaults no enablements", new(stubNixOS),
|
||||||
&hst.Config{Username: "chronos", Data: "/home/chronos"},
|
&hst.Config{Username: "chronos", Data: "/home/chronos"},
|
||||||
app.ID{
|
state.ID{
|
||||||
0x4a, 0x45, 0x0b, 0x65,
|
0x4a, 0x45, 0x0b, 0x65,
|
||||||
0x96, 0xd7, 0xbc, 0x15,
|
0x96, 0xd7, 0xbc, 0x15,
|
||||||
0xbd, 0x01, 0x78, 0x0e,
|
0xbd, 0x01, 0x78, 0x0e,
|
||||||
@@ -71,6 +71,7 @@ var testCasesPd = []sealTestCase{
|
|||||||
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel,
|
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel,
|
||||||
HostNet: true,
|
HostNet: true,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
|
ForwardCancel: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -115,7 +116,7 @@ var testCasesPd = []sealTestCase{
|
|||||||
},
|
},
|
||||||
Enablements: system.EWayland | system.EDBus | system.EPulse,
|
Enablements: system.EWayland | system.EDBus | system.EPulse,
|
||||||
},
|
},
|
||||||
app.ID{
|
state.ID{
|
||||||
0xeb, 0xf0, 0x83, 0xd1,
|
0xeb, 0xf0, 0x83, 0xd1,
|
||||||
0xb1, 0x75, 0x91, 0x17,
|
0xb1, 0x75, 0x91, 0x17,
|
||||||
0x82, 0xd4, 0x13, 0x36,
|
0x82, 0xd4, 0x13, 0x36,
|
||||||
@@ -220,6 +221,7 @@ var testCasesPd = []sealTestCase{
|
|||||||
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel,
|
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel,
|
||||||
HostNet: true,
|
HostNet: true,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
|
ForwardCancel: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package setuid_test
|
package app_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package common
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -19,9 +19,9 @@ import (
|
|||||||
// allocating slightly more as a margin for future expansion
|
// allocating slightly more as a margin for future expansion
|
||||||
const preallocateOpsCount = 1 << 5
|
const preallocateOpsCount = 1 << 5
|
||||||
|
|
||||||
// NewContainer initialises [sandbox.Params] via [hst.ContainerConfig].
|
// newContainer initialises [container.Params] via [hst.ContainerConfig].
|
||||||
// Note that remaining container setup must be queued by the caller.
|
// Note that remaining container setup must be queued by the caller.
|
||||||
func NewContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*container.Params, map[string]string, error) {
|
func newContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*container.Params, map[string]string, error) {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return nil, nil, syscall.EBADE
|
return nil, nil, syscall.EBADE
|
||||||
}
|
}
|
||||||
@@ -32,6 +32,10 @@ func NewContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain
|
|||||||
SeccompPresets: s.SeccompPresets,
|
SeccompPresets: s.SeccompPresets,
|
||||||
RetainSession: s.Tty,
|
RetainSession: s.Tty,
|
||||||
HostNet: s.Net,
|
HostNet: s.Net,
|
||||||
|
|
||||||
|
// the container is canceled when shim is requested to exit or receives an interrupt or termination signal;
|
||||||
|
// this behaviour is implemented in the shim
|
||||||
|
ForwardCancel: s.WaitDelay >= 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
package setuid
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
. "hakurei.app/cmd/hakurei/internal/app"
|
|
||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/hlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package setuid
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "hakurei.app/cmd/hakurei/internal/app"
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/sys"
|
"hakurei.app/internal/sys"
|
||||||
"hakurei.app/system"
|
"hakurei.app/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewWithID(id ID, os sys.State) App {
|
func NewWithID(id state.ID, os sys.State) App {
|
||||||
a := new(app)
|
a := new(app)
|
||||||
a.id = newID(&id)
|
a.id = newID(&id)
|
||||||
a.sys = os
|
a.sys = os
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package common
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package common
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package setuid
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -12,10 +12,9 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
. "hakurei.app/cmd/hakurei/internal/app"
|
|
||||||
"hakurei.app/cmd/hakurei/internal/state"
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/internal"
|
"hakurei.app/internal"
|
||||||
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/hlog"
|
||||||
"hakurei.app/system"
|
"hakurei.app/system"
|
||||||
)
|
)
|
||||||
@@ -124,7 +123,15 @@ func (seal *outcome) Run(rs *RunState) error {
|
|||||||
// this prevents blocking forever on an early failure
|
// this prevents blocking forever on an early failure
|
||||||
waitErr, setupErr := make(chan error, 1), make(chan error, 1)
|
waitErr, setupErr := make(chan error, 1), make(chan error, 1)
|
||||||
go func() { waitErr <- cmd.Wait(); cancel() }()
|
go func() { waitErr <- cmd.Wait(); cancel() }()
|
||||||
go func() { setupErr <- e.Encode(&shimParams{os.Getpid(), seal.container, seal.user.data, hlog.Load()}) }()
|
go func() {
|
||||||
|
setupErr <- e.Encode(&shimParams{
|
||||||
|
os.Getpid(),
|
||||||
|
seal.waitDelay,
|
||||||
|
seal.container,
|
||||||
|
seal.user.data,
|
||||||
|
hlog.Load(),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case err := <-setupErr:
|
case err := <-setupErr:
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package setuid
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -15,12 +15,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
. "hakurei.app/cmd/hakurei/internal/app"
|
|
||||||
"hakurei.app/cmd/hakurei/internal/app/instance/common"
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal"
|
"hakurei.app/internal"
|
||||||
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/hlog"
|
||||||
"hakurei.app/internal/sys"
|
"hakurei.app/internal/sys"
|
||||||
"hakurei.app/system"
|
"hakurei.app/system"
|
||||||
@@ -66,7 +66,7 @@ var posixUsername = regexp.MustCompilePOSIX("^[a-z_]([A-Za-z0-9_-]{0,31}|[A-Za-z
|
|||||||
// outcome stores copies of various parts of [hst.Config]
|
// outcome stores copies of various parts of [hst.Config]
|
||||||
type outcome struct {
|
type outcome struct {
|
||||||
// copied from initialising [app]
|
// copied from initialising [app]
|
||||||
id *stringPair[ID]
|
id *stringPair[state.ID]
|
||||||
// copied from [sys.State] response
|
// copied from [sys.State] response
|
||||||
runDirPath string
|
runDirPath string
|
||||||
|
|
||||||
@@ -80,6 +80,7 @@ type outcome struct {
|
|||||||
sys *system.I
|
sys *system.I
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
|
||||||
|
waitDelay time.Duration
|
||||||
container *container.Params
|
container *container.Params
|
||||||
env map[string]string
|
env map[string]string
|
||||||
sync *os.File
|
sync *os.File
|
||||||
@@ -281,7 +282,8 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
|||||||
{
|
{
|
||||||
var uid, gid int
|
var uid, gid int
|
||||||
var err error
|
var err error
|
||||||
seal.container, seal.env, err = common.NewContainer(config.Container, sys, &uid, &gid)
|
seal.container, seal.env, err = newContainer(config.Container, sys, &uid, &gid)
|
||||||
|
seal.waitDelay = config.Container.WaitDelay
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return hlog.WrapErrSuffix(err,
|
return hlog.WrapErrSuffix(err,
|
||||||
"cannot initialise container configuration:")
|
"cannot initialise container configuration:")
|
||||||
65
internal/app/shim-signal.c
Normal file
65
internal/app/shim-signal.c
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#include "shim-signal.h"
|
||||||
|
#include <errno.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
static pid_t hakurei_shim_param_ppid = -1;
|
||||||
|
static int hakurei_shim_fd = -1;
|
||||||
|
|
||||||
|
static ssize_t hakurei_shim_write(const void *buf, size_t count) {
|
||||||
|
int savedErrno = errno;
|
||||||
|
ssize_t ret = write(hakurei_shim_fd, buf, count);
|
||||||
|
if (ret == -1 && errno != EAGAIN)
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
errno = savedErrno;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* see shim_linux.go for handling of the value */
|
||||||
|
static void hakurei_shim_sigaction(int sig, siginfo_t *si, void *ucontext) {
|
||||||
|
if (sig != SIGCONT || si == NULL) {
|
||||||
|
/* unreachable */
|
||||||
|
hakurei_shim_write("\2", 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (si->si_pid == hakurei_shim_param_ppid) {
|
||||||
|
/* monitor requests shim exit */
|
||||||
|
hakurei_shim_write("\0", 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* unexpected si_pid */
|
||||||
|
hakurei_shim_write("\3", 1);
|
||||||
|
|
||||||
|
if (getppid() != hakurei_shim_param_ppid)
|
||||||
|
/* shim orphaned before monitor delivers a signal */
|
||||||
|
hakurei_shim_write("\1", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hakurei_shim_setup_cont_signal(pid_t ppid, int fd) {
|
||||||
|
if (hakurei_shim_param_ppid != -1 || hakurei_shim_fd != -1)
|
||||||
|
*(int *)NULL = 0; /* unreachable */
|
||||||
|
|
||||||
|
struct sigaction new_action = {0}, old_action = {0};
|
||||||
|
if (sigaction(SIGCONT, NULL, &old_action) != 0)
|
||||||
|
return;
|
||||||
|
if (old_action.sa_handler != SIG_DFL) {
|
||||||
|
errno = ENOTRECOVERABLE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_action.sa_sigaction = hakurei_shim_sigaction;
|
||||||
|
if (sigemptyset(&new_action.sa_mask) != 0)
|
||||||
|
return;
|
||||||
|
new_action.sa_flags = SA_ONSTACK | SA_SIGINFO;
|
||||||
|
|
||||||
|
if (sigaction(SIGCONT, &new_action, NULL) != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
hakurei_shim_param_ppid = ppid;
|
||||||
|
hakurei_shim_fd = fd;
|
||||||
|
}
|
||||||
3
internal/app/shim-signal.h
Normal file
3
internal/app/shim-signal.h
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
void hakurei_shim_setup_cont_signal(pid_t ppid, int fd);
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
package setuid
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -16,55 +19,7 @@ import (
|
|||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/hlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
//#include "shim-signal.h"
|
||||||
#include <stdlib.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <signal.h>
|
|
||||||
|
|
||||||
static pid_t hakurei_shim_param_ppid = -1;
|
|
||||||
|
|
||||||
// this cannot unblock hlog since Go code is not async-signal-safe
|
|
||||||
static void hakurei_shim_sigaction(int sig, siginfo_t *si, void *ucontext) {
|
|
||||||
if (sig != SIGCONT || si == NULL) {
|
|
||||||
// unreachable
|
|
||||||
fprintf(stderr, "sigaction: sa_sigaction got invalid siginfo\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// monitor requests shim exit
|
|
||||||
if (si->si_pid == hakurei_shim_param_ppid)
|
|
||||||
exit(254);
|
|
||||||
|
|
||||||
fprintf(stderr, "sigaction: got SIGCONT from process %d\n", si->si_pid);
|
|
||||||
|
|
||||||
// shim orphaned before monitor delivers a signal
|
|
||||||
if (getppid() != hakurei_shim_param_ppid)
|
|
||||||
exit(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
void hakurei_shim_setup_cont_signal(pid_t ppid) {
|
|
||||||
struct sigaction new_action = {0}, old_action = {0};
|
|
||||||
if (sigaction(SIGCONT, NULL, &old_action) != 0)
|
|
||||||
return;
|
|
||||||
if (old_action.sa_handler != SIG_DFL) {
|
|
||||||
errno = ENOTRECOVERABLE;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
new_action.sa_sigaction = hakurei_shim_sigaction;
|
|
||||||
if (sigemptyset(&new_action.sa_mask) != 0)
|
|
||||||
return;
|
|
||||||
new_action.sa_flags = SA_ONSTACK | SA_SIGINFO;
|
|
||||||
|
|
||||||
if (sigaction(SIGCONT, &new_action, NULL) != 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
errno = 0;
|
|
||||||
hakurei_shim_param_ppid = ppid;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
const shimEnv = "HAKUREI_SHIM"
|
const shimEnv = "HAKUREI_SHIM"
|
||||||
@@ -73,6 +28,10 @@ type shimParams struct {
|
|||||||
// monitor pid, checked against ppid in signal handler
|
// monitor pid, checked against ppid in signal handler
|
||||||
Monitor int
|
Monitor int
|
||||||
|
|
||||||
|
// duration to wait for after interrupting a container's initial process before the container is killed;
|
||||||
|
// zero value defaults to [DefaultShimWaitDelay], values exceeding [MaxShimWaitDelay] becomes [MaxShimWaitDelay]
|
||||||
|
WaitDelay time.Duration
|
||||||
|
|
||||||
// finalised container params
|
// finalised container params
|
||||||
Container *container.Params
|
Container *container.Params
|
||||||
// path to outer home directory
|
// path to outer home directory
|
||||||
@@ -82,6 +41,16 @@ type shimParams struct {
|
|||||||
Verbose bool
|
Verbose bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ShimExitRequest is returned when the monitor process requests shim exit.
|
||||||
|
ShimExitRequest = 254
|
||||||
|
// ShimExitOrphan is returned when the shim is orphaned before monitor delivers a signal.
|
||||||
|
ShimExitOrphan = 3
|
||||||
|
|
||||||
|
DefaultShimWaitDelay = 5 * time.Second
|
||||||
|
MaxShimWaitDelay = 30 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
// ShimMain is the main function of the shim process and runs as the unconstrained target user.
|
// ShimMain is the main function of the shim process and runs as the unconstrained target user.
|
||||||
func ShimMain() {
|
func ShimMain() {
|
||||||
hlog.Prepare("shim")
|
hlog.Prepare("shim")
|
||||||
@@ -106,18 +75,63 @@ func ShimMain() {
|
|||||||
} else {
|
} else {
|
||||||
internal.InstallOutput(params.Verbose)
|
internal.InstallOutput(params.Verbose)
|
||||||
closeSetup = f
|
closeSetup = f
|
||||||
|
|
||||||
// the Go runtime does not expose siginfo_t so SIGCONT is handled in C to check si_pid
|
|
||||||
if _, err = C.hakurei_shim_setup_cont_signal(C.pid_t(params.Monitor)); err != nil {
|
|
||||||
log.Fatalf("cannot install SIGCONT handler: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// pdeath_signal delivery is checked as if the dying process called kill(2), see kernel/exit.c
|
|
||||||
if _, _, errno := syscall.Syscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, uintptr(syscall.SIGCONT), 0); errno != 0 {
|
|
||||||
log.Fatalf("cannot set parent-death signal: %v", errno)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var signalPipe io.ReadCloser
|
||||||
|
// the Go runtime does not expose siginfo_t so SIGCONT is handled in C to check si_pid
|
||||||
|
if r, w, err := os.Pipe(); err != nil {
|
||||||
|
log.Fatalf("cannot pipe: %v", err)
|
||||||
|
} else if _, err = C.hakurei_shim_setup_cont_signal(C.pid_t(params.Monitor), C.int(w.Fd())); err != nil {
|
||||||
|
log.Fatalf("cannot install SIGCONT handler: %v", err)
|
||||||
|
} else {
|
||||||
|
defer runtime.KeepAlive(w)
|
||||||
|
signalPipe = r
|
||||||
|
}
|
||||||
|
|
||||||
|
// pdeath_signal delivery is checked as if the dying process called kill(2), see kernel/exit.c
|
||||||
|
if _, _, errno := syscall.Syscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, uintptr(syscall.SIGCONT), 0); errno != 0 {
|
||||||
|
log.Fatalf("cannot set parent-death signal: %v", errno)
|
||||||
|
}
|
||||||
|
|
||||||
|
// signal handler outcome
|
||||||
|
var cancelContainer atomic.Pointer[context.CancelFunc]
|
||||||
|
go func() {
|
||||||
|
buf := make([]byte, 1)
|
||||||
|
for {
|
||||||
|
if _, err := signalPipe.Read(buf); err != nil {
|
||||||
|
log.Fatalf("cannot read from signal pipe: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch buf[0] {
|
||||||
|
case 0: // got SIGCONT from monitor: shim exit requested
|
||||||
|
if fp := cancelContainer.Load(); params.Container.ForwardCancel && fp != nil && *fp != nil {
|
||||||
|
(*fp)()
|
||||||
|
// shim now bound by ShimWaitDelay, implemented below
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup has not completed, terminate immediately
|
||||||
|
hlog.Resume()
|
||||||
|
os.Exit(ShimExitRequest)
|
||||||
|
return
|
||||||
|
|
||||||
|
case 1: // got SIGCONT after adoption: monitor died before delivering signal
|
||||||
|
hlog.BeforeExit()
|
||||||
|
os.Exit(ShimExitOrphan)
|
||||||
|
return
|
||||||
|
|
||||||
|
case 2: // unreachable
|
||||||
|
log.Println("sa_sigaction got invalid siginfo")
|
||||||
|
|
||||||
|
case 3: // got SIGCONT from unexpected process: hopefully the terminal driver
|
||||||
|
log.Println("got SIGCONT from unexpected process")
|
||||||
|
|
||||||
|
default: // unreachable
|
||||||
|
log.Fatalf("got invalid message %d from signal handler", buf[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if params.Container == nil || params.Container.Ops == nil {
|
if params.Container == nil || params.Container.Ops == nil {
|
||||||
log.Fatal("invalid container params")
|
log.Fatal("invalid container params")
|
||||||
}
|
}
|
||||||
@@ -148,12 +162,18 @@ func ShimMain() {
|
|||||||
name = params.Container.Args[0]
|
name = params.Container.Args[0]
|
||||||
}
|
}
|
||||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||||
defer stop() // unreachable
|
cancelContainer.Store(&stop)
|
||||||
z := container.New(ctx, name)
|
z := container.New(ctx, name)
|
||||||
z.Params = *params.Container
|
z.Params = *params.Container
|
||||||
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
|
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
z.Cancel = func(cmd *exec.Cmd) error { return cmd.Process.Signal(os.Interrupt) }
|
|
||||||
z.WaitDelay = 2 * time.Second
|
z.WaitDelay = params.WaitDelay
|
||||||
|
if z.WaitDelay == 0 {
|
||||||
|
z.WaitDelay = DefaultShimWaitDelay
|
||||||
|
}
|
||||||
|
if z.WaitDelay > MaxShimWaitDelay {
|
||||||
|
z.WaitDelay = MaxShimWaitDelay
|
||||||
|
}
|
||||||
|
|
||||||
if err := z.Start(); err != nil {
|
if err := z.Start(); err != nil {
|
||||||
hlog.PrintBaseError(err, "cannot start container:")
|
hlog.PrintBaseError(err, "cannot start container:")
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package app
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
package app_test
|
package state_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "hakurei.app/cmd/hakurei/internal/app"
|
"hakurei.app/internal/app/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseAppID(t *testing.T) {
|
func TestParseAppID(t *testing.T) {
|
||||||
t.Run("bad length", func(t *testing.T) {
|
t.Run("bad length", func(t *testing.T) {
|
||||||
if err := ParseAppID(new(ID), "meow"); !errors.Is(err, ErrInvalidLength) {
|
if err := state.ParseAppID(new(state.ID), "meow"); !errors.Is(err, state.ErrInvalidLength) {
|
||||||
t.Errorf("ParseAppID: error = %v, wantErr = %v", err, ErrInvalidLength)
|
t.Errorf("ParseAppID: error = %v, wantErr = %v", err, state.ErrInvalidLength)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("bad byte", func(t *testing.T) {
|
t.Run("bad byte", func(t *testing.T) {
|
||||||
wantErr := "invalid char '\\n' at byte 15"
|
wantErr := "invalid char '\\n' at byte 15"
|
||||||
if err := ParseAppID(new(ID), "02bc7f8936b2af6\n\ne2535cd71ef0bb7"); err == nil || err.Error() != wantErr {
|
if err := state.ParseAppID(new(state.ID), "02bc7f8936b2af6\n\ne2535cd71ef0bb7"); err == nil || err.Error() != wantErr {
|
||||||
t.Errorf("ParseAppID: error = %v, wantErr = %v", err, wantErr)
|
t.Errorf("ParseAppID: error = %v, wantErr = %v", err, wantErr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -30,30 +30,30 @@ func TestParseAppID(t *testing.T) {
|
|||||||
|
|
||||||
func FuzzParseAppID(f *testing.F) {
|
func FuzzParseAppID(f *testing.F) {
|
||||||
for i := 0; i < 16; i++ {
|
for i := 0; i < 16; i++ {
|
||||||
id := new(ID)
|
id := new(state.ID)
|
||||||
if err := NewAppID(id); err != nil {
|
if err := state.NewAppID(id); err != nil {
|
||||||
panic(err.Error())
|
panic(err.Error())
|
||||||
}
|
}
|
||||||
f.Add(id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15])
|
f.Add(id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15])
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Fuzz(func(t *testing.T, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 byte) {
|
f.Fuzz(func(t *testing.T, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 byte) {
|
||||||
testParseAppID(t, &ID{b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15})
|
testParseAppID(t, &state.ID{b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testParseAppIDWithRandom(t *testing.T) {
|
func testParseAppIDWithRandom(t *testing.T) {
|
||||||
id := new(ID)
|
id := new(state.ID)
|
||||||
if err := NewAppID(id); err != nil {
|
if err := state.NewAppID(id); err != nil {
|
||||||
t.Fatalf("cannot generate app ID: %v", err)
|
t.Fatalf("cannot generate app ID: %v", err)
|
||||||
}
|
}
|
||||||
testParseAppID(t, id)
|
testParseAppID(t, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testParseAppID(t *testing.T, id *ID) {
|
func testParseAppID(t *testing.T, id *state.ID) {
|
||||||
s := id.String()
|
s := id.String()
|
||||||
got := new(ID)
|
got := new(state.ID)
|
||||||
if err := ParseAppID(got, s); err != nil {
|
if err := state.ParseAppID(got, s); err != nil {
|
||||||
t.Fatalf("cannot parse app ID: %v", err)
|
t.Fatalf("cannot parse app ID: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"hakurei.app/cmd/hakurei/internal/app"
|
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/hlog"
|
||||||
)
|
)
|
||||||
@@ -130,7 +129,7 @@ type multiBackend struct {
|
|||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *multiBackend) filename(id *app.ID) string {
|
func (b *multiBackend) filename(id *ID) string {
|
||||||
return path.Join(b.path, id.String())
|
return path.Join(b.path, id.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,8 +189,8 @@ func (b *multiBackend) load(decode bool) (Entries, error) {
|
|||||||
return nil, fmt.Errorf("unexpected directory %q in store", e.Name())
|
return nil, fmt.Errorf("unexpected directory %q in store", e.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
id := new(app.ID)
|
id := new(ID)
|
||||||
if err := app.ParseAppID(id, e.Name()); err != nil {
|
if err := ParseAppID(id, e.Name()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +335,7 @@ func (b *multiBackend) encodeState(w io.WriteSeeker, state *State, configWriter
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *multiBackend) Destroy(id app.ID) error {
|
func (b *multiBackend) Destroy(id ID) error {
|
||||||
b.lock.Lock()
|
b.lock.Lock()
|
||||||
defer b.lock.Unlock()
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
@@ -3,7 +3,7 @@ package state_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"hakurei.app/cmd/hakurei/internal/state"
|
"hakurei.app/internal/app/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMulti(t *testing.T) { testStore(t, state.NewMulti(t.TempDir())) }
|
func TestMulti(t *testing.T) { testStore(t, state.NewMulti(t.TempDir())) }
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// Package state provides cross-process state tracking for hakurei container instances.
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -5,13 +6,12 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/cmd/hakurei/internal/app"
|
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrNoConfig = errors.New("state does not contain config")
|
var ErrNoConfig = errors.New("state does not contain config")
|
||||||
|
|
||||||
type Entries map[app.ID]*State
|
type Entries map[ID]*State
|
||||||
|
|
||||||
type Store interface {
|
type Store interface {
|
||||||
// Do calls f exactly once and ensures store exclusivity until f returns.
|
// Do calls f exactly once and ensures store exclusivity until f returns.
|
||||||
@@ -30,7 +30,7 @@ type Store interface {
|
|||||||
// Cursor provides access to the store
|
// Cursor provides access to the store
|
||||||
type Cursor interface {
|
type Cursor interface {
|
||||||
Save(state *State, configWriter io.WriterTo) error
|
Save(state *State, configWriter io.WriterTo) error
|
||||||
Destroy(id app.ID) error
|
Destroy(id ID) error
|
||||||
Load() (Entries, error)
|
Load() (Entries, error)
|
||||||
Len() (int, error)
|
Len() (int, error)
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ type Cursor interface {
|
|||||||
// State is an instance state
|
// State is an instance state
|
||||||
type State struct {
|
type State struct {
|
||||||
// hakurei instance id
|
// hakurei instance id
|
||||||
ID app.ID `json:"instance"`
|
ID ID `json:"instance"`
|
||||||
// child process PID value
|
// child process PID value
|
||||||
PID int `json:"pid"`
|
PID int `json:"pid"`
|
||||||
// sealed app configuration
|
// sealed app configuration
|
||||||
@@ -10,9 +10,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/cmd/hakurei/internal/app"
|
|
||||||
"hakurei.app/cmd/hakurei/internal/state"
|
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal/app/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testStore(t *testing.T, s state.Store) {
|
func testStore(t *testing.T, s state.Store) {
|
||||||
@@ -134,7 +133,7 @@ func testStore(t *testing.T, s state.Store) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func makeState(t *testing.T, s *state.State, ct io.Writer) {
|
func makeState(t *testing.T, s *state.State, ct io.Writer) {
|
||||||
if err := app.NewAppID(&s.ID); err != nil {
|
if err := state.NewAppID(&s.ID); err != nil {
|
||||||
t.Fatalf("cannot create dummy state: %v", err)
|
t.Fatalf("cannot create dummy state: %v", err)
|
||||||
}
|
}
|
||||||
if err := gob.NewEncoder(ct).Encode(hst.Template()); err != nil {
|
if err := gob.NewEncoder(ct).Encode(hst.Template()); err != nil {
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package setuid
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
. "hakurei.app/cmd/hakurei/internal/app"
|
"hakurei.app/internal/app/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} }
|
func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} }
|
||||||
func newID(id *ID) *stringPair[ID] { return &stringPair[ID]{*id, id.String()} }
|
func newID(id *state.ID) *stringPair[state.ID] { return &stringPair[state.ID]{*id, id.String()} }
|
||||||
|
|
||||||
// stringPair stores a value and its string representation.
|
// stringPair stores a value and its string representation.
|
||||||
type stringPair[T comparable] struct {
|
type stringPair[T comparable] struct {
|
||||||
12
ldd/exec.go
12
ldd/exec.go
@@ -5,7 +5,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
@@ -19,16 +18,10 @@ var (
|
|||||||
msgStaticGlibc = []byte("not a dynamic executable")
|
msgStaticGlibc = []byte("not a dynamic executable")
|
||||||
)
|
)
|
||||||
|
|
||||||
func Exec(ctx context.Context, p string) ([]*Entry, error) { return ExecFilter(ctx, nil, nil, p) }
|
func Exec(ctx context.Context, p string) ([]*Entry, error) {
|
||||||
|
|
||||||
func ExecFilter(ctx context.Context,
|
|
||||||
commandContext func(context.Context) *exec.Cmd,
|
|
||||||
f func([]byte) []byte,
|
|
||||||
p string) ([]*Entry, error) {
|
|
||||||
c, cancel := context.WithTimeout(ctx, lddTimeout)
|
c, cancel := context.WithTimeout(ctx, lddTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
z := container.New(c, "ldd", p)
|
z := container.New(c, "ldd", p)
|
||||||
z.CommandContext = commandContext
|
|
||||||
z.Hostname = "hakurei-ldd"
|
z.Hostname = "hakurei-ldd"
|
||||||
z.SeccompFlags |= seccomp.AllowMultiarch
|
z.SeccompFlags |= seccomp.AllowMultiarch
|
||||||
z.SeccompPresets |= seccomp.PresetStrict
|
z.SeccompPresets |= seccomp.PresetStrict
|
||||||
@@ -54,8 +47,5 @@ func ExecFilter(ctx context.Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
v := stdout.Bytes()
|
v := stdout.Bytes()
|
||||||
if f != nil {
|
|
||||||
v = f(v)
|
|
||||||
}
|
|
||||||
return Parse(v)
|
return Parse(v)
|
||||||
}
|
}
|
||||||
|
|||||||
33
nixos.nix
33
nixos.nix
@@ -82,7 +82,8 @@ in
|
|||||||
own = [
|
own = [
|
||||||
"${id}.*"
|
"${id}.*"
|
||||||
"org.mpris.MediaPlayer2.${id}.*"
|
"org.mpris.MediaPlayer2.${id}.*"
|
||||||
] ++ ext.own;
|
]
|
||||||
|
++ ext.own;
|
||||||
|
|
||||||
inherit (ext) call broadcast;
|
inherit (ext) call broadcast;
|
||||||
};
|
};
|
||||||
@@ -127,6 +128,7 @@ in
|
|||||||
|
|
||||||
container = {
|
container = {
|
||||||
inherit (app)
|
inherit (app)
|
||||||
|
wait_delay
|
||||||
devel
|
devel
|
||||||
userns
|
userns
|
||||||
net
|
net
|
||||||
@@ -175,27 +177,26 @@ in
|
|||||||
auto_etc = true;
|
auto_etc = true;
|
||||||
cover = [ "/var/run/nscd" ];
|
cover = [ "/var/run/nscd" ];
|
||||||
|
|
||||||
symlink =
|
symlink = [
|
||||||
|
[
|
||||||
|
"*/run/current-system"
|
||||||
|
"/run/current-system"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
++ optionals (isGraphical && config.hardware.graphics.enable) (
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
"*/run/current-system"
|
config.systemd.tmpfiles.settings.graphics-driver."/run/opengl-driver"."L+".argument
|
||||||
"/run/current-system"
|
"/run/opengl-driver"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
++ optionals (isGraphical && config.hardware.graphics.enable) (
|
++ optionals (app.multiarch && config.hardware.graphics.enable32Bit) [
|
||||||
[
|
[
|
||||||
[
|
config.systemd.tmpfiles.settings.graphics-driver."/run/opengl-driver-32"."L+".argument
|
||||||
config.systemd.tmpfiles.settings.graphics-driver."/run/opengl-driver"."L+".argument
|
/run/opengl-driver-32
|
||||||
"/run/opengl-driver"
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
++ optionals (app.multiarch && config.hardware.graphics.enable32Bit) [
|
]
|
||||||
[
|
);
|
||||||
config.systemd.tmpfiles.settings.graphics-driver."/run/opengl-driver-32"."L+".argument
|
|
||||||
/run/opengl-driver-32
|
|
||||||
]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
11
options.nix
11
options.nix
@@ -76,6 +76,7 @@ in
|
|||||||
type =
|
type =
|
||||||
let
|
let
|
||||||
inherit (types)
|
inherit (types)
|
||||||
|
int
|
||||||
ints
|
ints
|
||||||
str
|
str
|
||||||
bool
|
bool
|
||||||
@@ -195,6 +196,16 @@ in
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
wait_delay = mkOption {
|
||||||
|
type = nullOr int;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Duration to wait for after interrupting a container's initial process in nanoseconds.
|
||||||
|
A negative value causes the container to be terminated immediately on cancellation.
|
||||||
|
Setting this to null defaults to five seconds.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
devel = mkEnableOption "debugging-related kernel interfaces";
|
devel = mkEnableOption "debugging-related kernel interfaces";
|
||||||
userns = mkEnableOption "user namespace creation";
|
userns = mkEnableOption "user namespace creation";
|
||||||
tty = mkEnableOption "access to the controlling terminal";
|
tty = mkEnableOption "access to the controlling terminal";
|
||||||
|
|||||||
48
package.nix
48
package.nix
@@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
buildGoModule rec {
|
buildGoModule rec {
|
||||||
pname = "hakurei";
|
pname = "hakurei";
|
||||||
version = "0.1.0";
|
version = "0.1.2";
|
||||||
|
|
||||||
srcFiltered = builtins.path {
|
srcFiltered = builtins.path {
|
||||||
name = "${pname}-src";
|
name = "${pname}-src";
|
||||||
@@ -83,18 +83,17 @@ buildGoModule rec {
|
|||||||
# nix build environment does not allow acls
|
# nix build environment does not allow acls
|
||||||
env.GO_TEST_SKIP_ACL = 1;
|
env.GO_TEST_SKIP_ACL = 1;
|
||||||
|
|
||||||
buildInputs =
|
buildInputs = [
|
||||||
[
|
libffi
|
||||||
libffi
|
libseccomp
|
||||||
libseccomp
|
acl
|
||||||
acl
|
wayland
|
||||||
wayland
|
]
|
||||||
]
|
++ (with xorg; [
|
||||||
++ (with xorg; [
|
libxcb
|
||||||
libxcb
|
libXau
|
||||||
libXau
|
libXdmcp
|
||||||
libXdmcp
|
]);
|
||||||
]);
|
|
||||||
|
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
pkg-config
|
pkg-config
|
||||||
@@ -130,17 +129,16 @@ buildGoModule rec {
|
|||||||
}
|
}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
passthru.targetPkgs =
|
passthru.targetPkgs = [
|
||||||
[
|
go
|
||||||
go
|
gcc
|
||||||
gcc
|
xorg.xorgproto
|
||||||
xorg.xorgproto
|
util-linux
|
||||||
util-linux
|
|
||||||
|
|
||||||
# for go generate
|
# for go generate
|
||||||
wayland-protocols
|
wayland-protocols
|
||||||
wayland-scanner
|
wayland-scanner
|
||||||
]
|
]
|
||||||
++ buildInputs
|
++ buildInputs
|
||||||
++ nativeBuildInputs;
|
++ nativeBuildInputs;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package system
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
"hakurei.app/system/acl"
|
"hakurei.app/system/acl"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -52,19 +53,19 @@ func TestACLString(t *testing.T) {
|
|||||||
et Enablement
|
et Enablement
|
||||||
perms []acl.Perm
|
perms []acl.Perm
|
||||||
}{
|
}{
|
||||||
{`--- type: process path: "/nonexistent"`, Process, []acl.Perm{}},
|
{`--- type: process path: "/proc/nonexistent"`, Process, []acl.Perm{}},
|
||||||
{`r-- type: user path: "/nonexistent"`, User, []acl.Perm{acl.Read}},
|
{`r-- type: user path: "/proc/nonexistent"`, User, []acl.Perm{acl.Read}},
|
||||||
{`-w- type: wayland path: "/nonexistent"`, EWayland, []acl.Perm{acl.Write}},
|
{`-w- type: wayland path: "/proc/nonexistent"`, EWayland, []acl.Perm{acl.Write}},
|
||||||
{`--x type: x11 path: "/nonexistent"`, EX11, []acl.Perm{acl.Execute}},
|
{`--x type: x11 path: "/proc/nonexistent"`, EX11, []acl.Perm{acl.Execute}},
|
||||||
{`rw- type: dbus path: "/nonexistent"`, EDBus, []acl.Perm{acl.Read, acl.Write}},
|
{`rw- type: dbus path: "/proc/nonexistent"`, EDBus, []acl.Perm{acl.Read, acl.Write}},
|
||||||
{`r-x type: pulseaudio path: "/nonexistent"`, EPulse, []acl.Perm{acl.Read, acl.Execute}},
|
{`r-x type: pulseaudio path: "/proc/nonexistent"`, EPulse, []acl.Perm{acl.Read, acl.Execute}},
|
||||||
{`rwx type: user path: "/nonexistent"`, User, []acl.Perm{acl.Read, acl.Write, acl.Execute}},
|
{`rwx type: user path: "/proc/nonexistent"`, User, []acl.Perm{acl.Read, acl.Write, acl.Execute}},
|
||||||
{`rwx type: process path: "/nonexistent"`, Process, []acl.Perm{acl.Read, acl.Write, acl.Write, acl.Execute}},
|
{`rwx type: process path: "/proc/nonexistent"`, Process, []acl.Perm{acl.Read, acl.Write, acl.Write, acl.Execute}},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.want, func(t *testing.T) {
|
t.Run(tc.want, func(t *testing.T) {
|
||||||
a := &ACL{et: tc.et, perms: tc.perms, path: "/nonexistent"}
|
a := &ACL{et: tc.et, perms: tc.perms, path: container.Nonexistent}
|
||||||
if got := a.String(); got != tc.want {
|
if got := a.String(); got != tc.want {
|
||||||
t.Errorf("String() = %v, want %v",
|
t.Errorf("String() = %v, want %v",
|
||||||
got, tc.want)
|
got, tc.want)
|
||||||
|
|||||||
@@ -1,22 +1,17 @@
|
|||||||
package dbus_test
|
package dbus_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/container"
|
|
||||||
"hakurei.app/helper"
|
"hakurei.app/helper"
|
||||||
"hakurei.app/internal"
|
|
||||||
"hakurei.app/internal/hlog"
|
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -64,20 +59,23 @@ func TestFinalise(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestProxyStartWaitCloseString(t *testing.T) {
|
func TestProxyStartWaitCloseString(t *testing.T) {
|
||||||
oldWaitDelay := helper.WaitDelay
|
t.Run("sandbox", func(t *testing.T) { testProxyFinaliseStartWaitCloseString(t, true) })
|
||||||
helper.WaitDelay = 16 * time.Second
|
|
||||||
t.Cleanup(func() { helper.WaitDelay = oldWaitDelay })
|
|
||||||
|
|
||||||
t.Run("sandbox", func(t *testing.T) {
|
|
||||||
proxyName := dbus.ProxyName
|
|
||||||
dbus.ProxyName = os.Args[0]
|
|
||||||
t.Cleanup(func() { dbus.ProxyName = proxyName })
|
|
||||||
testProxyFinaliseStartWaitCloseString(t, true)
|
|
||||||
})
|
|
||||||
t.Run("direct", func(t *testing.T) { testProxyFinaliseStartWaitCloseString(t, false) })
|
t.Run("direct", func(t *testing.T) { testProxyFinaliseStartWaitCloseString(t, false) })
|
||||||
}
|
}
|
||||||
|
|
||||||
func testProxyFinaliseStartWaitCloseString(t *testing.T, useSandbox bool) {
|
func testProxyFinaliseStartWaitCloseString(t *testing.T, useSandbox bool) {
|
||||||
|
{
|
||||||
|
oldWaitDelay := helper.WaitDelay
|
||||||
|
helper.WaitDelay = 16 * time.Second
|
||||||
|
t.Cleanup(func() { helper.WaitDelay = oldWaitDelay })
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
proxyName := dbus.ProxyName
|
||||||
|
dbus.ProxyName = os.Args[0]
|
||||||
|
t.Cleanup(func() { dbus.ProxyName = proxyName })
|
||||||
|
}
|
||||||
|
|
||||||
var p *dbus.Proxy
|
var p *dbus.Proxy
|
||||||
|
|
||||||
t.Run("string for nil proxy", func(t *testing.T) {
|
t.Run("string for nil proxy", func(t *testing.T) {
|
||||||
@@ -122,35 +120,12 @@ func testProxyFinaliseStartWaitCloseString(t *testing.T, useSandbox bool) {
|
|||||||
|
|
||||||
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if !useSandbox {
|
|
||||||
p = dbus.NewDirect(ctx, final, nil)
|
|
||||||
} else {
|
|
||||||
p = dbus.New(ctx, final, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
p.CommandContext = func(ctx context.Context) (cmd *exec.Cmd) {
|
|
||||||
return exec.CommandContext(ctx, os.Args[0], "-test.v",
|
|
||||||
"-test.run=TestHelperInit", "--", "init")
|
|
||||||
}
|
|
||||||
p.CmdF = func(v any) {
|
|
||||||
if useSandbox {
|
|
||||||
z := v.(*container.Container)
|
|
||||||
if z.Args[0] != dbus.ProxyName {
|
|
||||||
panic(fmt.Sprintf("unexpected argv0 %q", os.Args[0]))
|
|
||||||
}
|
|
||||||
z.Args = append([]string{os.Args[0], "-test.run=TestHelperStub", "--"}, z.Args[1:]...)
|
|
||||||
} else {
|
|
||||||
cmd := v.(*exec.Cmd)
|
|
||||||
if cmd.Args[0] != dbus.ProxyName {
|
|
||||||
panic(fmt.Sprintf("unexpected argv0 %q", os.Args[0]))
|
|
||||||
}
|
|
||||||
cmd.Err = nil
|
|
||||||
cmd.Path = os.Args[0]
|
|
||||||
cmd.Args = append([]string{os.Args[0], "-test.run=TestHelperStub", "--"}, cmd.Args[1:]...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.FilterF = func(v []byte) []byte { return bytes.SplitN(v, []byte("TestHelperInit\n"), 2)[1] }
|
|
||||||
output := new(strings.Builder)
|
output := new(strings.Builder)
|
||||||
|
if !useSandbox {
|
||||||
|
p = dbus.NewDirect(ctx, final, output)
|
||||||
|
} else {
|
||||||
|
p = dbus.New(ctx, final, output)
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("invalid wait", func(t *testing.T) {
|
t.Run("invalid wait", func(t *testing.T) {
|
||||||
wantErr := "dbus: not started"
|
wantErr := "dbus: not started"
|
||||||
@@ -176,9 +151,9 @@ func testProxyFinaliseStartWaitCloseString(t *testing.T, useSandbox bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Run("string", func(t *testing.T) {
|
t.Run("string", func(t *testing.T) {
|
||||||
wantSubstr := fmt.Sprintf("%s -test.run=TestHelperStub -- --args=3 --fd=4", os.Args[0])
|
wantSubstr := fmt.Sprintf("%s --args=3 --fd=4", os.Args[0])
|
||||||
if useSandbox {
|
if useSandbox {
|
||||||
wantSubstr = fmt.Sprintf(`argv: ["%s" "-test.run=TestHelperStub" "--" "--args=3" "--fd=4"], filter: true, rules: 0, flags: 0x1, presets: 0xf`, os.Args[0])
|
wantSubstr = fmt.Sprintf(`argv: ["%s" "--args=3" "--fd=4"], filter: true, rules: 0, flags: 0x1, presets: 0xf`, os.Args[0])
|
||||||
}
|
}
|
||||||
if got := p.String(); !strings.Contains(got, wantSubstr) {
|
if got := p.String(); !strings.Contains(got, wantSubstr) {
|
||||||
t.Errorf("String: %q, want %q",
|
t.Errorf("String: %q, want %q",
|
||||||
@@ -203,11 +178,3 @@ func testProxyFinaliseStartWaitCloseString(t *testing.T, useSandbox bool) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHelperInit(t *testing.T) {
|
|
||||||
if len(os.Args) != 5 || os.Args[4] != "init" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container.SetOutput(hlog.Output{})
|
|
||||||
container.Init(hlog.Prepare, internal.InstallOutput)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -36,9 +36,6 @@ func (p *Proxy) Start() error {
|
|||||||
|
|
||||||
if !p.useSandbox {
|
if !p.useSandbox {
|
||||||
p.helper = helper.NewDirect(ctx, p.name, p.final, true, argF, func(cmd *exec.Cmd) {
|
p.helper = helper.NewDirect(ctx, p.name, p.final, true, argF, func(cmd *exec.Cmd) {
|
||||||
if p.CmdF != nil {
|
|
||||||
p.CmdF(cmd)
|
|
||||||
}
|
|
||||||
if p.output != nil {
|
if p.output != nil {
|
||||||
cmd.Stdout, cmd.Stderr = p.output, p.output
|
cmd.Stdout, cmd.Stderr = p.output, p.output
|
||||||
}
|
}
|
||||||
@@ -56,7 +53,7 @@ func (p *Proxy) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var libPaths []string
|
var libPaths []string
|
||||||
if entries, err := ldd.ExecFilter(ctx, p.CommandContext, p.FilterF, toolPath); err != nil {
|
if entries, err := ldd.Exec(ctx, toolPath); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
libPaths = ldd.Path(entries)
|
libPaths = ldd.Path(entries)
|
||||||
@@ -69,15 +66,10 @@ func (p *Proxy) Start() error {
|
|||||||
z.SeccompFlags |= seccomp.AllowMultiarch
|
z.SeccompFlags |= seccomp.AllowMultiarch
|
||||||
z.SeccompPresets |= seccomp.PresetStrict
|
z.SeccompPresets |= seccomp.PresetStrict
|
||||||
z.Hostname = "hakurei-dbus"
|
z.Hostname = "hakurei-dbus"
|
||||||
z.CommandContext = p.CommandContext
|
|
||||||
if p.output != nil {
|
if p.output != nil {
|
||||||
z.Stdout, z.Stderr = p.output, p.output
|
z.Stdout, z.Stderr = p.output, p.output
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.CmdF != nil {
|
|
||||||
p.CmdF(z)
|
|
||||||
}
|
|
||||||
|
|
||||||
// these lib paths are unpredictable, so mount them first so they cannot cover anything
|
// these lib paths are unpredictable, so mount them first so they cannot cover anything
|
||||||
for _, name := range libPaths {
|
for _, name := range libPaths {
|
||||||
z.Bind(name, name, 0)
|
z.Bind(name, name, 0)
|
||||||
|
|||||||
17
system/dbus/proc_test.go
Normal file
17
system/dbus/proc_test.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package dbus_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/helper"
|
||||||
|
"hakurei.app/internal"
|
||||||
|
"hakurei.app/internal/hlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput)
|
||||||
|
helper.InternalHelperStub()
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os/exec"
|
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
@@ -37,10 +36,6 @@ type Proxy struct {
|
|||||||
useSandbox bool
|
useSandbox bool
|
||||||
|
|
||||||
name string
|
name string
|
||||||
CmdF func(any)
|
|
||||||
|
|
||||||
CommandContext func(ctx context.Context) (cmd *exec.Cmd)
|
|
||||||
FilterF func([]byte) []byte
|
|
||||||
|
|
||||||
mu, pmu sync.RWMutex
|
mu, pmu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
package dbus_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"hakurei.app/helper"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHelperStub(t *testing.T) { helper.InternalHelperStub() }
|
|
||||||
@@ -3,6 +3,8 @@ package system
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEnsure(t *testing.T) {
|
func TestEnsure(t *testing.T) {
|
||||||
@@ -60,11 +62,11 @@ func TestMkdirString(t *testing.T) {
|
|||||||
t.Run(tc.want, func(t *testing.T) {
|
t.Run(tc.want, func(t *testing.T) {
|
||||||
m := &Mkdir{
|
m := &Mkdir{
|
||||||
et: tc.et,
|
et: tc.et,
|
||||||
path: "/nonexistent",
|
path: container.Nonexistent,
|
||||||
perm: 0701,
|
perm: 0701,
|
||||||
ephemeral: tc.ephemeral,
|
ephemeral: tc.ephemeral,
|
||||||
}
|
}
|
||||||
want := "mode: " + os.FileMode(0701).String() + " type: " + tc.want + " path: \"/nonexistent\""
|
want := "mode: " + os.FileMode(0701).String() + " type: " + tc.want + ` path: "/proc/nonexistent"`
|
||||||
if got := m.String(); got != want {
|
if got := m.String(); got != want {
|
||||||
t.Errorf("String() = %v, want %v", got, want)
|
t.Errorf("String() = %v, want %v", got, want)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,6 +127,21 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
"cat.gensokyo.extern.foot.noEnablements.immediate" = {
|
||||||
|
name = "ne-foot-immediate";
|
||||||
|
identity = 1;
|
||||||
|
shareUid = true;
|
||||||
|
verbose = true;
|
||||||
|
wait_delay = -1;
|
||||||
|
share = pkgs.foot;
|
||||||
|
packages = [ ];
|
||||||
|
command = "foot";
|
||||||
|
capability = {
|
||||||
|
dbus = false;
|
||||||
|
pulse = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
"cat.gensokyo.extern.foot.pulseaudio" = {
|
"cat.gensokyo.extern.foot.pulseaudio" = {
|
||||||
name = "pa-foot";
|
name = "pa-foot";
|
||||||
identity = 2;
|
identity = 2;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build testtool
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Package sandbox provides utilities for checking sandbox outcome.
|
Package sandbox provides utilities for checking sandbox outcome.
|
||||||
|
|
||||||
@@ -15,7 +17,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -40,13 +41,10 @@ type T struct {
|
|||||||
MountsPath string
|
MountsPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *T) MustCheckFile(wantFilePath, markerPath string) {
|
func (t *T) MustCheckFile(wantFilePath string) {
|
||||||
var want *TestCase
|
var want *TestCase
|
||||||
mustDecode(wantFilePath, &want)
|
mustDecode(wantFilePath, &want)
|
||||||
t.MustCheck(want)
|
t.MustCheck(want)
|
||||||
if _, err := os.Create(markerPath); err != nil {
|
|
||||||
fatalf("cannot create success marker: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *T) MustCheck(want *TestCase) {
|
func (t *T) MustCheck(want *TestCase) {
|
||||||
@@ -165,31 +163,10 @@ func CheckFilter(pid int, want string) error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
h := sha512.New()
|
h := sha512.New()
|
||||||
{
|
|
||||||
getFilter:
|
|
||||||
buf, err := getFilter[[8]byte](pid, 0)
|
|
||||||
/* this is not how ESRCH should be handled: the manpage advises the
|
|
||||||
use of waitpid, however that is not applicable for attaching to an
|
|
||||||
arbitrary process, and spawning target process here is not easily
|
|
||||||
possible under the current testing framework;
|
|
||||||
|
|
||||||
despite checking for /proc/pid/status indicating state t (tracing stop),
|
|
||||||
it does not appear to be directly related to the internal state used to
|
|
||||||
determine whether a process is ready to accept ptrace operations, it also
|
|
||||||
introduces a TOCTOU that is irrelevant in the testing vm; this behaviour
|
|
||||||
is kept anyway as it reduces the average iterations required here;
|
|
||||||
|
|
||||||
since this code is only ever compiled into the test program, whatever
|
|
||||||
implications this ugliness might have should not hurt anyone */
|
|
||||||
if errors.Is(err, syscall.ESRCH) {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
goto getFilter
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if buf, err := getFilter[[8]byte](pid, 0); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
for _, b := range buf {
|
for _, b := range buf {
|
||||||
h.Write(b[:])
|
h.Write(b[:])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build testtool
|
||||||
|
|
||||||
package sandbox
|
package sandbox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
lib: testProgram:
|
system: lib: testProgram:
|
||||||
let
|
let
|
||||||
fs = mode: dir: data: {
|
fs = mode: dir: data: {
|
||||||
mode = lib.fromHexString mode;
|
mode = lib.fromHexString mode;
|
||||||
@@ -31,6 +31,7 @@ let
|
|||||||
fs
|
fs
|
||||||
ent
|
ent
|
||||||
ignore
|
ignore
|
||||||
|
system
|
||||||
;
|
;
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
@@ -43,13 +44,17 @@ let
|
|||||||
device
|
device
|
||||||
mapRealUid
|
mapRealUid
|
||||||
useCommonPaths
|
useCommonPaths
|
||||||
|
userns
|
||||||
;
|
;
|
||||||
share = testProgram;
|
share = testProgram;
|
||||||
packages = [ ];
|
packages = [ ];
|
||||||
path = "${testProgram}/bin/hakurei-test";
|
path = "${testProgram}/bin/hakurei-test";
|
||||||
args = [
|
args = [
|
||||||
"test"
|
"test"
|
||||||
|
"-t"
|
||||||
(toString (builtins.toFile "hakurei-${tc.name}-want.json" (builtins.toJSON tc.want)))
|
(toString (builtins.toFile "hakurei-${tc.name}-want.json" (builtins.toJSON tc.want)))
|
||||||
|
"-s"
|
||||||
|
tc.expectedFilter.${system}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,4 +65,5 @@ in
|
|||||||
${testCaseName "tty"} = callTestCase ./tty.nix 2;
|
${testCaseName "tty"} = callTestCase ./tty.nix 2;
|
||||||
${testCaseName "mapuid"} = callTestCase ./mapuid.nix 3;
|
${testCaseName "mapuid"} = callTestCase ./mapuid.nix 3;
|
||||||
${testCaseName "device"} = callTestCase ./device.nix 4;
|
${testCaseName "device"} = callTestCase ./device.nix 4;
|
||||||
|
${testCaseName "pdlike"} = callTestCase ./pdlike.nix 5;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,35 @@
|
|||||||
fs,
|
fs,
|
||||||
ent,
|
ent,
|
||||||
ignore,
|
ignore,
|
||||||
|
system,
|
||||||
}:
|
}:
|
||||||
|
let
|
||||||
|
extraPaths = {
|
||||||
|
x86_64-linux = {
|
||||||
|
fd = "fd0";
|
||||||
|
sr = {
|
||||||
|
sr0 = fs "80001ff" null null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
aarch64-linux = {
|
||||||
|
fd = "mtdblock0";
|
||||||
|
sr = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
{
|
{
|
||||||
name = "device";
|
name = "device";
|
||||||
tty = false;
|
tty = false;
|
||||||
device = true;
|
device = true;
|
||||||
mapRealUid = false;
|
mapRealUid = false;
|
||||||
useCommonPaths = true;
|
useCommonPaths = true;
|
||||||
|
userns = false;
|
||||||
|
|
||||||
|
# 0, PresetStrict
|
||||||
|
expectedFilter = {
|
||||||
|
x86_64-linux = "e880298df2bd6751d0040fc21bc0ed4c00f95dc0d7ba506c244d8b8cf6866dba8ef4a33296f287b66cccc1d78e97026597f84cc7dec1573e148960fbd35cd735";
|
||||||
|
aarch64-linux = "79318538a3dc851314b6bd96f10d5861acb2aa7e13cb8de0619d0f6a76709d67f01ef3fd67e195862b02f9711e5b769bc4d1eb4fc0dfc41a723c89c968a93297";
|
||||||
|
};
|
||||||
|
|
||||||
want = {
|
want = {
|
||||||
env = [
|
env = [
|
||||||
@@ -113,19 +135,21 @@
|
|||||||
} null;
|
} null;
|
||||||
} null;
|
} null;
|
||||||
sys = fs "800001c0" {
|
sys = fs "800001c0" {
|
||||||
block = fs "800001ed" {
|
block = fs "800001ed" (
|
||||||
fd0 = fs "80001ff" null null;
|
{
|
||||||
loop0 = fs "80001ff" null null;
|
${extraPaths.${system}.fd} = fs "80001ff" null null;
|
||||||
loop1 = fs "80001ff" null null;
|
loop0 = fs "80001ff" null null;
|
||||||
loop2 = fs "80001ff" null null;
|
loop1 = fs "80001ff" null null;
|
||||||
loop3 = fs "80001ff" null null;
|
loop2 = fs "80001ff" null null;
|
||||||
loop4 = fs "80001ff" null null;
|
loop3 = fs "80001ff" null null;
|
||||||
loop5 = fs "80001ff" null null;
|
loop4 = fs "80001ff" null null;
|
||||||
loop6 = fs "80001ff" null null;
|
loop5 = fs "80001ff" null null;
|
||||||
loop7 = fs "80001ff" null null;
|
loop6 = fs "80001ff" null null;
|
||||||
sr0 = fs "80001ff" null null;
|
loop7 = fs "80001ff" null null;
|
||||||
vda = fs "80001ff" null null;
|
vda = fs "80001ff" null null;
|
||||||
} null;
|
}
|
||||||
|
// extraPaths.${system}.sr
|
||||||
|
) null;
|
||||||
bus = fs "800001ed" null null;
|
bus = fs "800001ed" null null;
|
||||||
class = fs "800001ed" null null;
|
class = fs "800001ed" null null;
|
||||||
dev = fs "800001ed" {
|
dev = fs "800001ed" {
|
||||||
@@ -149,13 +173,6 @@
|
|||||||
} null;
|
} null;
|
||||||
} null;
|
} null;
|
||||||
".local" = fs "800001ed" {
|
".local" = fs "800001ed" {
|
||||||
share = fs "800001ed" {
|
|
||||||
dbus-1 = fs "800001ed" {
|
|
||||||
services = fs "800001ed" {
|
|
||||||
"ca.desrt.dconf.service" = fs "80001ff" null null;
|
|
||||||
} null;
|
|
||||||
} null;
|
|
||||||
} null;
|
|
||||||
state = fs "800001ed" {
|
state = fs "800001ed" {
|
||||||
".keep" = fs "80001ff" null "";
|
".keep" = fs "80001ff" null "";
|
||||||
home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null;
|
home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null;
|
||||||
|
|||||||
@@ -2,13 +2,44 @@
|
|||||||
fs,
|
fs,
|
||||||
ent,
|
ent,
|
||||||
ignore,
|
ignore,
|
||||||
|
system,
|
||||||
}:
|
}:
|
||||||
|
let
|
||||||
|
extraPaths = {
|
||||||
|
x86_64-linux = {
|
||||||
|
fd = "fd0";
|
||||||
|
"/dev/dri" = {
|
||||||
|
by-path = fs "800001ed" {
|
||||||
|
"pci-0000:00:09.0-card" = fs "80001ff" null null;
|
||||||
|
"pci-0000:00:09.0-render" = fs "80001ff" null null;
|
||||||
|
} null;
|
||||||
|
card0 = fs "42001b0" null null;
|
||||||
|
renderD128 = fs "42001b6" null null;
|
||||||
|
};
|
||||||
|
sr = {
|
||||||
|
sr0 = fs "80001ff" null null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
aarch64-linux = {
|
||||||
|
fd = "mtdblock0";
|
||||||
|
"/dev/dri" = null;
|
||||||
|
sr = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
{
|
{
|
||||||
name = "mapuid";
|
name = "mapuid";
|
||||||
tty = false;
|
tty = false;
|
||||||
device = false;
|
device = false;
|
||||||
mapRealUid = true;
|
mapRealUid = true;
|
||||||
useCommonPaths = true;
|
useCommonPaths = true;
|
||||||
|
userns = false;
|
||||||
|
|
||||||
|
# 0, PresetStrict
|
||||||
|
expectedFilter = {
|
||||||
|
x86_64-linux = "e880298df2bd6751d0040fc21bc0ed4c00f95dc0d7ba506c244d8b8cf6866dba8ef4a33296f287b66cccc1d78e97026597f84cc7dec1573e148960fbd35cd735";
|
||||||
|
aarch64-linux = "79318538a3dc851314b6bd96f10d5861acb2aa7e13cb8de0619d0f6a76709d67f01ef3fd67e195862b02f9711e5b769bc4d1eb4fc0dfc41a723c89c968a93297";
|
||||||
|
};
|
||||||
|
|
||||||
want = {
|
want = {
|
||||||
env = [
|
env = [
|
||||||
@@ -29,14 +60,7 @@
|
|||||||
bin = fs "800001ed" { sh = fs "80001ff" null null; } null;
|
bin = fs "800001ed" { sh = fs "80001ff" null null; } null;
|
||||||
dev = fs "800001ed" {
|
dev = fs "800001ed" {
|
||||||
core = fs "80001ff" null null;
|
core = fs "80001ff" null null;
|
||||||
dri = fs "800001ed" {
|
dri = fs "800001ed" extraPaths.${system}."/dev/dri" null;
|
||||||
by-path = fs "800001ed" {
|
|
||||||
"pci-0000:00:09.0-card" = fs "80001ff" null null;
|
|
||||||
"pci-0000:00:09.0-render" = fs "80001ff" null null;
|
|
||||||
} null;
|
|
||||||
card0 = fs "42001b0" null null;
|
|
||||||
renderD128 = fs "42001b6" null null;
|
|
||||||
} null;
|
|
||||||
fd = fs "80001ff" null null;
|
fd = fs "80001ff" null null;
|
||||||
full = fs "42001b6" null null;
|
full = fs "42001b6" null null;
|
||||||
mqueue = fs "801001ff" { } null;
|
mqueue = fs "801001ff" { } null;
|
||||||
@@ -137,19 +161,21 @@
|
|||||||
} null;
|
} null;
|
||||||
} null;
|
} null;
|
||||||
sys = fs "800001c0" {
|
sys = fs "800001c0" {
|
||||||
block = fs "800001ed" {
|
block = fs "800001ed" (
|
||||||
fd0 = fs "80001ff" null null;
|
{
|
||||||
loop0 = fs "80001ff" null null;
|
${extraPaths.${system}.fd} = fs "80001ff" null null;
|
||||||
loop1 = fs "80001ff" null null;
|
loop0 = fs "80001ff" null null;
|
||||||
loop2 = fs "80001ff" null null;
|
loop1 = fs "80001ff" null null;
|
||||||
loop3 = fs "80001ff" null null;
|
loop2 = fs "80001ff" null null;
|
||||||
loop4 = fs "80001ff" null null;
|
loop3 = fs "80001ff" null null;
|
||||||
loop5 = fs "80001ff" null null;
|
loop4 = fs "80001ff" null null;
|
||||||
loop6 = fs "80001ff" null null;
|
loop5 = fs "80001ff" null null;
|
||||||
loop7 = fs "80001ff" null null;
|
loop6 = fs "80001ff" null null;
|
||||||
sr0 = fs "80001ff" null null;
|
loop7 = fs "80001ff" null null;
|
||||||
vda = fs "80001ff" null null;
|
vda = fs "80001ff" null null;
|
||||||
} null;
|
}
|
||||||
|
// extraPaths.${system}.sr
|
||||||
|
) null;
|
||||||
bus = fs "800001ed" null null;
|
bus = fs "800001ed" null null;
|
||||||
class = fs "800001ed" null null;
|
class = fs "800001ed" null null;
|
||||||
dev = fs "800001ed" {
|
dev = fs "800001ed" {
|
||||||
@@ -173,13 +199,6 @@
|
|||||||
} null;
|
} null;
|
||||||
} null;
|
} null;
|
||||||
".local" = fs "800001ed" {
|
".local" = fs "800001ed" {
|
||||||
share = fs "800001ed" {
|
|
||||||
dbus-1 = fs "800001ed" {
|
|
||||||
services = fs "800001ed" {
|
|
||||||
"ca.desrt.dconf.service" = fs "80001ff" null null;
|
|
||||||
} null;
|
|
||||||
} null;
|
|
||||||
} null;
|
|
||||||
state = fs "800001ed" {
|
state = fs "800001ed" {
|
||||||
".keep" = fs "80001ff" null "";
|
".keep" = fs "80001ff" null "";
|
||||||
home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null;
|
home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null;
|
||||||
|
|||||||
267
test/sandbox/case/pdlike.nix
Normal file
267
test/sandbox/case/pdlike.nix
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
{
|
||||||
|
fs,
|
||||||
|
ent,
|
||||||
|
ignore,
|
||||||
|
system,
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
extraPaths = {
|
||||||
|
x86_64-linux = {
|
||||||
|
fd = "fd0";
|
||||||
|
"/dev/dri" = {
|
||||||
|
by-path = fs "800001ed" {
|
||||||
|
"pci-0000:00:09.0-card" = fs "80001ff" null null;
|
||||||
|
"pci-0000:00:09.0-render" = fs "80001ff" null null;
|
||||||
|
} null;
|
||||||
|
card0 = fs "42001b0" null null;
|
||||||
|
renderD128 = fs "42001b6" null null;
|
||||||
|
};
|
||||||
|
sr = {
|
||||||
|
sr0 = fs "80001ff" null null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
aarch64-linux = {
|
||||||
|
fd = "mtdblock0";
|
||||||
|
"/dev/dri" = null;
|
||||||
|
sr = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
name = "pdlike";
|
||||||
|
tty = true;
|
||||||
|
device = false;
|
||||||
|
mapRealUid = false;
|
||||||
|
useCommonPaths = false;
|
||||||
|
userns = true;
|
||||||
|
|
||||||
|
# 0, PresetExt | PresetDenyDevel
|
||||||
|
expectedFilter = {
|
||||||
|
x86_64-linux = "c698b081ff957afe17a6d94374537d37f2a63f6f9dd75da7546542407a9e32476ebda3312ba7785d7f618542bcfaf27ca27dcc2dddba852069d28bcfe8cad39a";
|
||||||
|
aarch64-linux = "433ce9b911282d6dcc8029319fb79b816b60d5a795ec8fc94344dd027614d68f023166a91bb881faaeeedd26e3d89474e141e5a69a97e93b8984ca8f14999980";
|
||||||
|
};
|
||||||
|
|
||||||
|
want = {
|
||||||
|
env = [
|
||||||
|
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus"
|
||||||
|
"HOME=/var/lib/hakurei/u0/a5"
|
||||||
|
"PULSE_SERVER=unix:/run/user/65534/pulse/native"
|
||||||
|
"SHELL=/run/current-system/sw/bin/bash"
|
||||||
|
"TERM=linux"
|
||||||
|
"USER=u0_a5"
|
||||||
|
"WAYLAND_DISPLAY=wayland-0"
|
||||||
|
"XDG_RUNTIME_DIR=/run/user/65534"
|
||||||
|
"XDG_SESSION_CLASS=user"
|
||||||
|
"XDG_SESSION_TYPE=tty"
|
||||||
|
];
|
||||||
|
|
||||||
|
fs = fs "dead" {
|
||||||
|
".hakurei" = fs "800001ed" { } null;
|
||||||
|
bin = fs "800001ed" { sh = fs "80001ff" null null; } null;
|
||||||
|
dev = fs "800001ed" {
|
||||||
|
console = fs "4200190" null null;
|
||||||
|
core = fs "80001ff" null null;
|
||||||
|
dri = fs "800001ed" extraPaths.${system}."/dev/dri" null;
|
||||||
|
fd = fs "80001ff" null null;
|
||||||
|
full = fs "42001b6" null null;
|
||||||
|
mqueue = fs "801001ff" { } null;
|
||||||
|
null = fs "42001b6" null "";
|
||||||
|
ptmx = fs "80001ff" null null;
|
||||||
|
pts = fs "800001ed" { ptmx = fs "42001b6" null null; } null;
|
||||||
|
random = fs "42001b6" null null;
|
||||||
|
shm = fs "800001ed" { } null;
|
||||||
|
stderr = fs "80001ff" null null;
|
||||||
|
stdin = fs "80001ff" null null;
|
||||||
|
stdout = fs "80001ff" null null;
|
||||||
|
tty = fs "42001b6" null null;
|
||||||
|
urandom = fs "42001b6" null null;
|
||||||
|
zero = fs "42001b6" null null;
|
||||||
|
} null;
|
||||||
|
etc = fs "800001ed" {
|
||||||
|
".clean" = fs "80001ff" null null;
|
||||||
|
".host" = fs "800001c0" null null;
|
||||||
|
".updated" = fs "80001ff" null null;
|
||||||
|
"NIXOS" = fs "80001ff" null null;
|
||||||
|
"X11" = fs "80001ff" null null;
|
||||||
|
"alsa" = fs "80001ff" null null;
|
||||||
|
"bash_logout" = fs "80001ff" null null;
|
||||||
|
"bashrc" = fs "80001ff" null null;
|
||||||
|
"binfmt.d" = fs "80001ff" null null;
|
||||||
|
"dbus-1" = fs "80001ff" null null;
|
||||||
|
"default" = fs "80001ff" null null;
|
||||||
|
"dhcpcd.exit-hook" = fs "80001ff" null null;
|
||||||
|
"fonts" = fs "80001ff" null null;
|
||||||
|
"fstab" = fs "80001ff" null null;
|
||||||
|
"hsurc" = fs "80001ff" null null;
|
||||||
|
"fuse.conf" = fs "80001ff" null null;
|
||||||
|
"group" = fs "180" null "hakurei:x:65534:\n";
|
||||||
|
"host.conf" = fs "80001ff" null null;
|
||||||
|
"hostname" = fs "80001ff" null null;
|
||||||
|
"hosts" = fs "80001ff" null null;
|
||||||
|
"inputrc" = fs "80001ff" null null;
|
||||||
|
"issue" = fs "80001ff" null null;
|
||||||
|
"kbd" = fs "80001ff" null null;
|
||||||
|
"locale.conf" = fs "80001ff" null null;
|
||||||
|
"login.defs" = fs "80001ff" null null;
|
||||||
|
"lsb-release" = fs "80001ff" null null;
|
||||||
|
"lvm" = fs "80001ff" null null;
|
||||||
|
"machine-id" = fs "80001ff" null null;
|
||||||
|
"man_db.conf" = fs "80001ff" null null;
|
||||||
|
"modprobe.d" = fs "80001ff" null null;
|
||||||
|
"modules-load.d" = fs "80001ff" null null;
|
||||||
|
"mtab" = fs "80001ff" null null;
|
||||||
|
"nanorc" = fs "80001ff" null null;
|
||||||
|
"netgroup" = fs "80001ff" null null;
|
||||||
|
"nix" = fs "80001ff" null null;
|
||||||
|
"nixos" = fs "80001ff" null null;
|
||||||
|
"nscd.conf" = fs "80001ff" null null;
|
||||||
|
"nsswitch.conf" = fs "80001ff" null null;
|
||||||
|
"os-release" = fs "80001ff" null null;
|
||||||
|
"pam" = fs "80001ff" null null;
|
||||||
|
"pam.d" = fs "80001ff" null null;
|
||||||
|
"passwd" = fs "180" null "u0_a5:x:65534:65534:Hakurei:/var/lib/hakurei/u0/a5:/run/current-system/sw/bin/bash\n";
|
||||||
|
"pipewire" = fs "80001ff" null null;
|
||||||
|
"pki" = fs "80001ff" null null;
|
||||||
|
"polkit-1" = fs "80001ff" null null;
|
||||||
|
"profile" = fs "80001ff" null null;
|
||||||
|
"protocols" = fs "80001ff" null null;
|
||||||
|
"resolv.conf" = fs "80001ff" null null;
|
||||||
|
"resolvconf.conf" = fs "80001ff" null null;
|
||||||
|
"rpc" = fs "80001ff" null null;
|
||||||
|
"services" = fs "80001ff" null null;
|
||||||
|
"set-environment" = fs "80001ff" null null;
|
||||||
|
"shadow" = fs "80001ff" null null;
|
||||||
|
"shells" = fs "80001ff" null null;
|
||||||
|
"ssh" = fs "80001ff" null null;
|
||||||
|
"ssl" = fs "80001ff" null null;
|
||||||
|
"static" = fs "80001ff" null null;
|
||||||
|
"subgid" = fs "80001ff" null null;
|
||||||
|
"subuid" = fs "80001ff" null null;
|
||||||
|
"sudoers" = fs "80001ff" null null;
|
||||||
|
"sway" = fs "80001ff" null null;
|
||||||
|
"sysctl.d" = fs "80001ff" null null;
|
||||||
|
"systemd" = fs "80001ff" null null;
|
||||||
|
"terminfo" = fs "80001ff" null null;
|
||||||
|
"tmpfiles.d" = fs "80001ff" null null;
|
||||||
|
"udev" = fs "80001ff" null null;
|
||||||
|
"vconsole.conf" = fs "80001ff" null null;
|
||||||
|
"xdg" = fs "80001ff" null null;
|
||||||
|
"zoneinfo" = fs "80001ff" null null;
|
||||||
|
} null;
|
||||||
|
nix = fs "800001c0" { store = fs "801001fd" null null; } null;
|
||||||
|
proc = fs "8000016d" null null;
|
||||||
|
run = fs "800001ed" {
|
||||||
|
current-system = fs "80001ff" null null;
|
||||||
|
opengl-driver = fs "80001ff" null null;
|
||||||
|
user = fs "800001ed" {
|
||||||
|
"65534" = fs "800001f8" {
|
||||||
|
bus = fs "10001fd" null null;
|
||||||
|
pulse = fs "800001c0" { native = fs "10001b6" null null; } null;
|
||||||
|
wayland-0 = fs "1000038" null null;
|
||||||
|
} null;
|
||||||
|
} null;
|
||||||
|
} null;
|
||||||
|
sys = fs "800001c0" {
|
||||||
|
block = fs "800001ed" (
|
||||||
|
{
|
||||||
|
${extraPaths.${system}.fd} = fs "80001ff" null null;
|
||||||
|
loop0 = fs "80001ff" null null;
|
||||||
|
loop1 = fs "80001ff" null null;
|
||||||
|
loop2 = fs "80001ff" null null;
|
||||||
|
loop3 = fs "80001ff" null null;
|
||||||
|
loop4 = fs "80001ff" null null;
|
||||||
|
loop5 = fs "80001ff" null null;
|
||||||
|
loop6 = fs "80001ff" null null;
|
||||||
|
loop7 = fs "80001ff" null null;
|
||||||
|
vda = fs "80001ff" null null;
|
||||||
|
}
|
||||||
|
// extraPaths.${system}.sr
|
||||||
|
) null;
|
||||||
|
bus = fs "800001ed" null null;
|
||||||
|
class = fs "800001ed" null null;
|
||||||
|
dev = fs "800001ed" {
|
||||||
|
block = fs "800001ed" null null;
|
||||||
|
char = fs "800001ed" null null;
|
||||||
|
} null;
|
||||||
|
devices = fs "800001ed" null null;
|
||||||
|
} null;
|
||||||
|
tmp = fs "800001f8" { } null;
|
||||||
|
usr = fs "800001c0" { bin = fs "800001ed" { env = fs "80001ff" null null; } null; } null;
|
||||||
|
var = fs "800001c0" {
|
||||||
|
lib = fs "800001c0" {
|
||||||
|
hakurei = fs "800001c0" {
|
||||||
|
u0 = fs "800001c0" {
|
||||||
|
a5 = fs "800001c0" {
|
||||||
|
".cache" = fs "800001ed" { ".keep" = fs "80001ff" null ""; } null;
|
||||||
|
".config" = fs "800001ed" {
|
||||||
|
"environment.d" = fs "800001ed" { "10-home-manager.conf" = fs "80001ff" null null; } null;
|
||||||
|
systemd = fs "800001ed" {
|
||||||
|
user = fs "800001ed" { "tray.target" = fs "80001ff" null null; } null;
|
||||||
|
} null;
|
||||||
|
} null;
|
||||||
|
".local" = fs "800001ed" {
|
||||||
|
state = fs "800001ed" {
|
||||||
|
".keep" = fs "80001ff" null "";
|
||||||
|
home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null;
|
||||||
|
nix = fs "800001ed" {
|
||||||
|
profiles = fs "800001ed" {
|
||||||
|
home-manager = fs "80001ff" null null;
|
||||||
|
home-manager-1-link = fs "80001ff" null null;
|
||||||
|
profile = fs "80001ff" null null;
|
||||||
|
profile-1-link = fs "80001ff" null null;
|
||||||
|
} null;
|
||||||
|
} null;
|
||||||
|
} null;
|
||||||
|
} null;
|
||||||
|
".nix-defexpr" = fs "800001ed" {
|
||||||
|
channels = fs "80001ff" null null;
|
||||||
|
channels_root = fs "80001ff" null null;
|
||||||
|
} null;
|
||||||
|
".nix-profile" = fs "80001ff" null null;
|
||||||
|
} null;
|
||||||
|
} null;
|
||||||
|
} null;
|
||||||
|
} null;
|
||||||
|
run = fs "800001ed" { nscd = fs "800001ed" { } null; } null;
|
||||||
|
} null;
|
||||||
|
} null;
|
||||||
|
|
||||||
|
mount = [
|
||||||
|
(ent "/sysroot" "/" "rw,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000005,gid=1000005")
|
||||||
|
(ent "/" "/proc" "rw,nosuid,nodev,noexec,relatime" "proc" "proc" "rw")
|
||||||
|
(ent "/" "/.hakurei" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000005,gid=1000005")
|
||||||
|
(ent "/" "/dev" "rw,nosuid,nodev,relatime" "tmpfs" "devtmpfs" "rw,mode=755,uid=1000005,gid=1000005")
|
||||||
|
(ent "/null" "/dev/null" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||||
|
(ent "/zero" "/dev/zero" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||||
|
(ent "/full" "/dev/full" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||||
|
(ent "/random" "/dev/random" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||||
|
(ent "/urandom" "/dev/urandom" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||||
|
(ent "/tty" "/dev/tty" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||||
|
(ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,mode=620,ptmxmode=666")
|
||||||
|
(ent ignore "/dev/console" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666")
|
||||||
|
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
|
||||||
|
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent "/" "/nix/store" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
|
||||||
|
(ent "/block" "/sys/block" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
||||||
|
(ent "/bus" "/sys/bus" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
||||||
|
(ent "/class" "/sys/class" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
||||||
|
(ent "/dev" "/sys/dev" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
||||||
|
(ent "/devices" "/sys/devices" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
||||||
|
(ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||||
|
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000005,gid=1000005")
|
||||||
|
(ent "/tmp/hakurei.1000/runtime/5" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent "/tmp/hakurei.1000/tmpdir/5" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent "/var/lib/hakurei/u0/a5" "/var/lib/hakurei/u0/a5" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000005,gid=1000005")
|
||||||
|
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000005,gid=1000005")
|
||||||
|
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent ignore "/run/user/65534/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
|
||||||
|
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent "/" "/var/run/nscd" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8k,mode=755,uid=1000005,gid=1000005")
|
||||||
|
];
|
||||||
|
|
||||||
|
seccomp = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -2,13 +2,44 @@
|
|||||||
fs,
|
fs,
|
||||||
ent,
|
ent,
|
||||||
ignore,
|
ignore,
|
||||||
|
system,
|
||||||
}:
|
}:
|
||||||
|
let
|
||||||
|
extraPaths = {
|
||||||
|
x86_64-linux = {
|
||||||
|
fd = "fd0";
|
||||||
|
"/dev/dri" = {
|
||||||
|
by-path = fs "800001ed" {
|
||||||
|
"pci-0000:00:09.0-card" = fs "80001ff" null null;
|
||||||
|
"pci-0000:00:09.0-render" = fs "80001ff" null null;
|
||||||
|
} null;
|
||||||
|
card0 = fs "42001b0" null null;
|
||||||
|
renderD128 = fs "42001b6" null null;
|
||||||
|
};
|
||||||
|
sr = {
|
||||||
|
sr0 = fs "80001ff" null null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
aarch64-linux = {
|
||||||
|
fd = "mtdblock0";
|
||||||
|
"/dev/dri" = null;
|
||||||
|
sr = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
{
|
{
|
||||||
name = "preset";
|
name = "preset";
|
||||||
tty = false;
|
tty = false;
|
||||||
device = false;
|
device = false;
|
||||||
mapRealUid = false;
|
mapRealUid = false;
|
||||||
useCommonPaths = false;
|
useCommonPaths = false;
|
||||||
|
userns = false;
|
||||||
|
|
||||||
|
# 0, PresetStrict
|
||||||
|
expectedFilter = {
|
||||||
|
x86_64-linux = "e880298df2bd6751d0040fc21bc0ed4c00f95dc0d7ba506c244d8b8cf6866dba8ef4a33296f287b66cccc1d78e97026597f84cc7dec1573e148960fbd35cd735";
|
||||||
|
aarch64-linux = "79318538a3dc851314b6bd96f10d5861acb2aa7e13cb8de0619d0f6a76709d67f01ef3fd67e195862b02f9711e5b769bc4d1eb4fc0dfc41a723c89c968a93297";
|
||||||
|
};
|
||||||
|
|
||||||
want = {
|
want = {
|
||||||
env = [
|
env = [
|
||||||
@@ -29,14 +60,7 @@
|
|||||||
bin = fs "800001ed" { sh = fs "80001ff" null null; } null;
|
bin = fs "800001ed" { sh = fs "80001ff" null null; } null;
|
||||||
dev = fs "800001ed" {
|
dev = fs "800001ed" {
|
||||||
core = fs "80001ff" null null;
|
core = fs "80001ff" null null;
|
||||||
dri = fs "800001ed" {
|
dri = fs "800001ed" extraPaths.${system}."/dev/dri" null;
|
||||||
by-path = fs "800001ed" {
|
|
||||||
"pci-0000:00:09.0-card" = fs "80001ff" null null;
|
|
||||||
"pci-0000:00:09.0-render" = fs "80001ff" null null;
|
|
||||||
} null;
|
|
||||||
card0 = fs "42001b0" null null;
|
|
||||||
renderD128 = fs "42001b6" null null;
|
|
||||||
} null;
|
|
||||||
fd = fs "80001ff" null null;
|
fd = fs "80001ff" null null;
|
||||||
full = fs "42001b6" null null;
|
full = fs "42001b6" null null;
|
||||||
mqueue = fs "801001ff" { } null;
|
mqueue = fs "801001ff" { } null;
|
||||||
@@ -137,19 +161,21 @@
|
|||||||
} null;
|
} null;
|
||||||
} null;
|
} null;
|
||||||
sys = fs "800001c0" {
|
sys = fs "800001c0" {
|
||||||
block = fs "800001ed" {
|
block = fs "800001ed" (
|
||||||
fd0 = fs "80001ff" null null;
|
{
|
||||||
loop0 = fs "80001ff" null null;
|
${extraPaths.${system}.fd} = fs "80001ff" null null;
|
||||||
loop1 = fs "80001ff" null null;
|
loop0 = fs "80001ff" null null;
|
||||||
loop2 = fs "80001ff" null null;
|
loop1 = fs "80001ff" null null;
|
||||||
loop3 = fs "80001ff" null null;
|
loop2 = fs "80001ff" null null;
|
||||||
loop4 = fs "80001ff" null null;
|
loop3 = fs "80001ff" null null;
|
||||||
loop5 = fs "80001ff" null null;
|
loop4 = fs "80001ff" null null;
|
||||||
loop6 = fs "80001ff" null null;
|
loop5 = fs "80001ff" null null;
|
||||||
loop7 = fs "80001ff" null null;
|
loop6 = fs "80001ff" null null;
|
||||||
sr0 = fs "80001ff" null null;
|
loop7 = fs "80001ff" null null;
|
||||||
vda = fs "80001ff" null null;
|
vda = fs "80001ff" null null;
|
||||||
} null;
|
}
|
||||||
|
// extraPaths.${system}.sr
|
||||||
|
) null;
|
||||||
bus = fs "800001ed" null null;
|
bus = fs "800001ed" null null;
|
||||||
class = fs "800001ed" null null;
|
class = fs "800001ed" null null;
|
||||||
dev = fs "800001ed" {
|
dev = fs "800001ed" {
|
||||||
@@ -173,13 +199,6 @@
|
|||||||
} null;
|
} null;
|
||||||
} null;
|
} null;
|
||||||
".local" = fs "800001ed" {
|
".local" = fs "800001ed" {
|
||||||
share = fs "800001ed" {
|
|
||||||
dbus-1 = fs "800001ed" {
|
|
||||||
services = fs "800001ed" {
|
|
||||||
"ca.desrt.dconf.service" = fs "80001ff" null null;
|
|
||||||
} null;
|
|
||||||
} null;
|
|
||||||
} null;
|
|
||||||
state = fs "800001ed" {
|
state = fs "800001ed" {
|
||||||
".keep" = fs "80001ff" null "";
|
".keep" = fs "80001ff" null "";
|
||||||
home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null;
|
home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null;
|
||||||
|
|||||||
@@ -2,13 +2,44 @@
|
|||||||
fs,
|
fs,
|
||||||
ent,
|
ent,
|
||||||
ignore,
|
ignore,
|
||||||
|
system,
|
||||||
}:
|
}:
|
||||||
|
let
|
||||||
|
extraPaths = {
|
||||||
|
x86_64-linux = {
|
||||||
|
fd = "fd0";
|
||||||
|
"/dev/dri" = {
|
||||||
|
by-path = fs "800001ed" {
|
||||||
|
"pci-0000:00:09.0-card" = fs "80001ff" null null;
|
||||||
|
"pci-0000:00:09.0-render" = fs "80001ff" null null;
|
||||||
|
} null;
|
||||||
|
card0 = fs "42001b0" null null;
|
||||||
|
renderD128 = fs "42001b6" null null;
|
||||||
|
};
|
||||||
|
sr = {
|
||||||
|
sr0 = fs "80001ff" null null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
aarch64-linux = {
|
||||||
|
fd = "mtdblock0";
|
||||||
|
"/dev/dri" = null;
|
||||||
|
sr = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
{
|
{
|
||||||
name = "tty";
|
name = "tty";
|
||||||
tty = true;
|
tty = true;
|
||||||
device = false;
|
device = false;
|
||||||
mapRealUid = false;
|
mapRealUid = false;
|
||||||
useCommonPaths = true;
|
useCommonPaths = true;
|
||||||
|
userns = false;
|
||||||
|
|
||||||
|
# 0, PresetExt | PresetDenyNS | PresetDenyDevel
|
||||||
|
expectedFilter = {
|
||||||
|
x86_64-linux = "0b76007476c1c9e25dbf674c29fdf609a1656a70063e49327654e1b5360ad3da06e1a3e32bf80e961c5516ad83d4b9e7e9bde876a93797e27627d2555c25858b";
|
||||||
|
aarch64-linux = "cf1f4dc87436ba8ec95d268b663a6397bb0b4a5ac64d8557e6cc529d8b0f6f65dad3a92b62ed29d85eee9c6dde1267757a4d0f86032e8a45ca1bceadfa34cf5e";
|
||||||
|
};
|
||||||
|
|
||||||
want = {
|
want = {
|
||||||
env = [
|
env = [
|
||||||
@@ -30,14 +61,7 @@
|
|||||||
dev = fs "800001ed" {
|
dev = fs "800001ed" {
|
||||||
console = fs "4200190" null null;
|
console = fs "4200190" null null;
|
||||||
core = fs "80001ff" null null;
|
core = fs "80001ff" null null;
|
||||||
dri = fs "800001ed" {
|
dri = fs "800001ed" extraPaths.${system}."/dev/dri" null;
|
||||||
by-path = fs "800001ed" {
|
|
||||||
"pci-0000:00:09.0-card" = fs "80001ff" null null;
|
|
||||||
"pci-0000:00:09.0-render" = fs "80001ff" null null;
|
|
||||||
} null;
|
|
||||||
card0 = fs "42001b0" null null;
|
|
||||||
renderD128 = fs "42001b6" null null;
|
|
||||||
} null;
|
|
||||||
fd = fs "80001ff" null null;
|
fd = fs "80001ff" null null;
|
||||||
full = fs "42001b6" null null;
|
full = fs "42001b6" null null;
|
||||||
mqueue = fs "801001ff" { } null;
|
mqueue = fs "801001ff" { } null;
|
||||||
@@ -138,19 +162,21 @@
|
|||||||
} null;
|
} null;
|
||||||
} null;
|
} null;
|
||||||
sys = fs "800001c0" {
|
sys = fs "800001c0" {
|
||||||
block = fs "800001ed" {
|
block = fs "800001ed" (
|
||||||
fd0 = fs "80001ff" null null;
|
{
|
||||||
loop0 = fs "80001ff" null null;
|
${extraPaths.${system}.fd} = fs "80001ff" null null;
|
||||||
loop1 = fs "80001ff" null null;
|
loop0 = fs "80001ff" null null;
|
||||||
loop2 = fs "80001ff" null null;
|
loop1 = fs "80001ff" null null;
|
||||||
loop3 = fs "80001ff" null null;
|
loop2 = fs "80001ff" null null;
|
||||||
loop4 = fs "80001ff" null null;
|
loop3 = fs "80001ff" null null;
|
||||||
loop5 = fs "80001ff" null null;
|
loop4 = fs "80001ff" null null;
|
||||||
loop6 = fs "80001ff" null null;
|
loop5 = fs "80001ff" null null;
|
||||||
loop7 = fs "80001ff" null null;
|
loop6 = fs "80001ff" null null;
|
||||||
sr0 = fs "80001ff" null null;
|
loop7 = fs "80001ff" null null;
|
||||||
vda = fs "80001ff" null null;
|
vda = fs "80001ff" null null;
|
||||||
} null;
|
}
|
||||||
|
// extraPaths.${system}.sr
|
||||||
|
) null;
|
||||||
bus = fs "800001ed" null null;
|
bus = fs "800001ed" null null;
|
||||||
class = fs "800001ed" null null;
|
class = fs "800001ed" null null;
|
||||||
dev = fs "800001ed" {
|
dev = fs "800001ed" {
|
||||||
@@ -174,13 +200,6 @@
|
|||||||
} null;
|
} null;
|
||||||
} null;
|
} null;
|
||||||
".local" = fs "800001ed" {
|
".local" = fs "800001ed" {
|
||||||
share = fs "800001ed" {
|
|
||||||
dbus-1 = fs "800001ed" {
|
|
||||||
services = fs "800001ed" {
|
|
||||||
"ca.desrt.dconf.service" = fs "80001ff" null null;
|
|
||||||
} null;
|
|
||||||
} null;
|
|
||||||
} null;
|
|
||||||
state = fs "800001ed" {
|
state = fs "800001ed" {
|
||||||
".keep" = fs "80001ff" null "";
|
".keep" = fs "80001ff" null "";
|
||||||
home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null;
|
home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null;
|
||||||
|
|||||||
@@ -75,6 +75,6 @@ in
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
apps = import ./case lib testProgram;
|
apps = import ./case pkgs.system lib testProgram;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build testtool
|
||||||
|
|
||||||
package sandbox
|
package sandbox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build testtool
|
||||||
|
|
||||||
package sandbox_test
|
package sandbox_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build testtool
|
||||||
|
|
||||||
package sandbox
|
package sandbox
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build testtool
|
||||||
|
|
||||||
package sandbox_test
|
package sandbox_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
|
//go:build testtool
|
||||||
|
|
||||||
package sandbox
|
package sandbox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,49 +37,19 @@ func ptrace(op uintptr, pid, addr int, data unsafe.Pointer) (r uintptr, errno sy
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ptraceAttach(pid int) error {
|
func ptraceAttach(pid int) error {
|
||||||
const (
|
|
||||||
statePrefix = "State:"
|
|
||||||
stateSuffix = "t (tracing stop)"
|
|
||||||
)
|
|
||||||
|
|
||||||
var r io.ReadSeekCloser
|
|
||||||
if f, err := os.Open(fmt.Sprintf("/proc/%d/status", pid)); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
r = f
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, errno := ptrace(PTRACE_ATTACH, pid, 0, nil); errno != 0 {
|
if _, errno := ptrace(PTRACE_ATTACH, pid, 0, nil); errno != 0 {
|
||||||
return &ptraceError{"PTRACE_ATTACH", errno}
|
return &ptraceError{"PTRACE_ATTACH", errno}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ugly! but there does not appear to be another way
|
var status syscall.WaitStatus
|
||||||
for {
|
for {
|
||||||
time.Sleep(10 * time.Millisecond)
|
if _, err := syscall.Wait4(pid, &status, syscall.WALL, nil); err != nil {
|
||||||
|
if errors.Is(err, syscall.EINTR) {
|
||||||
if _, err := r.Seek(0, io.SeekStart); err != nil {
|
continue
|
||||||
return err
|
|
||||||
}
|
|
||||||
s := bufio.NewScanner(r)
|
|
||||||
|
|
||||||
var found bool
|
|
||||||
for s.Scan() {
|
|
||||||
found = strings.HasPrefix(s.Text(), statePrefix)
|
|
||||||
if found {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
fatalf("cannot waitpid: %v", err)
|
||||||
}
|
}
|
||||||
if err := s.Err(); err != nil {
|
break
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
return syscall.EBADE
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasSuffix(s.Text(), stateSuffix) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build testtool
|
||||||
|
|
||||||
package sandbox
|
package sandbox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -18,7 +20,6 @@ func trySyscalls() error {
|
|||||||
trap, a1, a2, a3, a4, a5, a6 uintptr
|
trap, a1, a2, a3, a4, a5, a6 uintptr
|
||||||
}{
|
}{
|
||||||
{"syslog", syscall.EPERM, syscall.SYS_SYSLOG, 0, NULL, NULL, NULL, NULL, NULL},
|
{"syslog", syscall.EPERM, syscall.SYS_SYSLOG, 0, NULL, NULL, NULL, NULL, NULL},
|
||||||
{"uselib", syscall.EPERM, syscall.SYS_USELIB, 0, NULL, NULL, NULL, NULL, NULL},
|
|
||||||
{"acct", syscall.EPERM, syscall.SYS_ACCT, 0, NULL, NULL, NULL, NULL, NULL},
|
{"acct", syscall.EPERM, syscall.SYS_ACCT, 0, NULL, NULL, NULL, NULL, NULL},
|
||||||
{"quotactl", syscall.EPERM, syscall.SYS_QUOTACTL, C.Q_GETQUOTA, NULL, uintptr(os.Getuid()), NULL, NULL, NULL},
|
{"quotactl", syscall.EPERM, syscall.SYS_QUOTACTL, C.Q_GETQUOTA, NULL, uintptr(os.Getuid()), NULL, NULL, NULL},
|
||||||
{"add_key", syscall.EPERM, syscall.SYS_ADD_KEY, NULL, NULL, NULL, NULL, NULL, NULL},
|
{"add_key", syscall.EPERM, syscall.SYS_ADD_KEY, NULL, NULL, NULL, NULL, NULL, NULL},
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ def swaymsg(command: str = "", succeed=True, type="command"):
|
|||||||
return parsed
|
return parsed
|
||||||
|
|
||||||
|
|
||||||
|
def check_filter(check_offset, name, pname):
|
||||||
|
pid = int(machine.wait_until_succeeds(f"pgrep -U {1000000+check_offset} -x {pname}", timeout=15))
|
||||||
|
hash = machine.succeed(f"sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 WAYLAND_DISPLAY=wayland-1 check-sandbox-{name} hash 2>/dev/null")
|
||||||
|
print(machine.succeed(f"hakurei-test -s {hash} filter {pid}"))
|
||||||
|
|
||||||
|
|
||||||
start_all()
|
start_all()
|
||||||
machine.wait_for_unit("multi-user.target")
|
machine.wait_for_unit("multi-user.target")
|
||||||
|
|
||||||
@@ -35,11 +41,9 @@ print(machine.succeed("sudo -u alice -i hakurei version"))
|
|||||||
machine.wait_for_file("/run/user/1000/wayland-1")
|
machine.wait_for_file("/run/user/1000/wayland-1")
|
||||||
machine.wait_for_file("/tmp/sway-ipc.sock")
|
machine.wait_for_file("/tmp/sway-ipc.sock")
|
||||||
|
|
||||||
# Check seccomp outcome:
|
# Check pd seccomp outcome:
|
||||||
swaymsg("exec hakurei run cat")
|
swaymsg("exec hakurei run cat")
|
||||||
pid = int(machine.wait_until_succeeds("pgrep -U 1000000 -x cat", timeout=5))
|
check_filter(0, "pdlike", "cat")
|
||||||
print(machine.succeed(f"hakurei-test filter {pid} c698b081ff957afe17a6d94374537d37f2a63f6f9dd75da7546542407a9e32476ebda3312ba7785d7f618542bcfaf27ca27dcc2dddba852069d28bcfe8cad39a &>/dev/stdout", timeout=5))
|
|
||||||
machine.succeed(f"kill -TERM {pid}")
|
|
||||||
|
|
||||||
# Verify capabilities/securebits in user namespace:
|
# Verify capabilities/securebits in user namespace:
|
||||||
print(machine.succeed("sudo -u alice -i hakurei run capsh --print"))
|
print(machine.succeed("sudo -u alice -i hakurei run capsh --print"))
|
||||||
@@ -57,12 +61,14 @@ def check_sandbox(name):
|
|||||||
check_offset += 1
|
check_offset += 1
|
||||||
swaymsg(f"exec script /dev/null -E always -qec check-sandbox-{name}")
|
swaymsg(f"exec script /dev/null -E always -qec check-sandbox-{name}")
|
||||||
machine.wait_for_file(f"/tmp/hakurei.1000/tmpdir/{check_offset}/sandbox-ok", timeout=15)
|
machine.wait_for_file(f"/tmp/hakurei.1000/tmpdir/{check_offset}/sandbox-ok", timeout=15)
|
||||||
|
check_filter(check_offset, name, "hakurei-test")
|
||||||
|
|
||||||
|
|
||||||
check_sandbox("preset")
|
check_sandbox("preset")
|
||||||
check_sandbox("tty")
|
check_sandbox("tty")
|
||||||
check_sandbox("mapuid")
|
check_sandbox("mapuid")
|
||||||
check_sandbox("device")
|
check_sandbox("device")
|
||||||
|
check_sandbox("pdlike")
|
||||||
|
|
||||||
# Exit Sway and verify process exit status 0:
|
# Exit Sway and verify process exit status 0:
|
||||||
swaymsg("exit", succeed=False)
|
swaymsg("exit", succeed=False)
|
||||||
|
|||||||
@@ -1,39 +1,71 @@
|
|||||||
|
//go:build testtool
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"hakurei.app/test/sandbox"
|
"hakurei.app/test/sandbox"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagTestCase string
|
||||||
|
flagBpfHash string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.StringVar(&flagTestCase, "t", "", "Nix store path to test case file")
|
||||||
|
flag.StringVar(&flagBpfHash, "s", "", "String representation of expected bpf sha512 hash")
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
log.SetPrefix("test: ")
|
log.SetPrefix("test: ")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
if len(os.Args) < 2 {
|
args := flag.Args()
|
||||||
log.Fatal("invalid argument")
|
if len(args) < 1 {
|
||||||
|
s := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(s, syscall.SIGINT)
|
||||||
|
go func() { <-s; log.Println("exiting on signal (likely from verifier)"); os.Exit(0) }()
|
||||||
|
|
||||||
|
(&sandbox.T{FS: os.DirFS("/")}).MustCheckFile(flagTestCase)
|
||||||
|
if _, err := os.Create("/tmp/sandbox-ok"); err != nil {
|
||||||
|
log.Fatalf("cannot create success marker: %v", err)
|
||||||
|
}
|
||||||
|
log.Println("blocking for seccomp check")
|
||||||
|
select {}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch os.Args[1] {
|
switch args[0] {
|
||||||
case "filter":
|
case "filter":
|
||||||
if len(os.Args) != 4 {
|
if len(args) != 2 {
|
||||||
log.Fatal("invalid argument")
|
log.Fatal("invalid argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
if pid, err := strconv.Atoi(strings.TrimSpace(os.Args[2])); err != nil {
|
if pid, err := strconv.Atoi(strings.TrimSpace(args[1])); err != nil {
|
||||||
log.Fatalf("%s", err)
|
log.Fatalf("%s", err)
|
||||||
} else if pid < 1 {
|
} else if pid < 1 {
|
||||||
log.Fatalf("%d out of range", pid)
|
log.Fatalf("%d out of range", pid)
|
||||||
} else {
|
} else {
|
||||||
sandbox.MustCheckFilter(pid, os.Args[3])
|
sandbox.MustCheckFilter(pid, flagBpfHash)
|
||||||
return
|
if err = syscall.Kill(pid, syscall.SIGINT); err != nil {
|
||||||
|
log.Fatalf("cannot signal check process: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "hash": // this eases the pain of passing the hash to python
|
||||||
|
fmt.Print(flagBpfHash)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
(&sandbox.T{FS: os.DirFS("/")}).MustCheckFile(os.Args[1], "/tmp/sandbox-ok")
|
log.Fatal("invalid argument")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ buildGoModule rec {
|
|||||||
};
|
};
|
||||||
vendorHash = null;
|
vendorHash = null;
|
||||||
|
|
||||||
|
tags = [ "testtool" ];
|
||||||
|
|
||||||
buildInputs = [ util-linux ];
|
buildInputs = [ util-linux ];
|
||||||
nativeBuildInputs = [ pkg-config ];
|
nativeBuildInputs = [ pkg-config ];
|
||||||
|
|
||||||
|
|||||||
19
test/test.py
19
test/test.py
@@ -178,9 +178,28 @@ machine.succeed("pkill -INT -f 'hakurei -v app '")
|
|||||||
machine.wait_until_fails("pgrep foot", timeout=5)
|
machine.wait_until_fails("pgrep foot", timeout=5)
|
||||||
machine.wait_for_file("/tmp/monitor-exit-code")
|
machine.wait_for_file("/tmp/monitor-exit-code")
|
||||||
interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code"))
|
interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code"))
|
||||||
|
if interrupt_exit_code != 230:
|
||||||
|
raise Exception(f"unexpected exit code {interrupt_exit_code}")
|
||||||
|
|
||||||
|
# Check interrupt shim behaviour immediate termination:
|
||||||
|
swaymsg("exec sh -c 'ne-foot-immediate; echo -n $? > /tmp/monitor-exit-code'")
|
||||||
|
wait_for_window(f"u0_a{aid(0)}@machine")
|
||||||
|
machine.succeed("pkill -INT -f 'hakurei -v app '")
|
||||||
|
machine.wait_until_fails("pgrep foot", timeout=5)
|
||||||
|
machine.wait_for_file("/tmp/monitor-exit-code")
|
||||||
|
interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code"))
|
||||||
if interrupt_exit_code != 254:
|
if interrupt_exit_code != 254:
|
||||||
raise Exception(f"unexpected exit code {interrupt_exit_code}")
|
raise Exception(f"unexpected exit code {interrupt_exit_code}")
|
||||||
|
|
||||||
|
# Check shim SIGCONT from unexpected process behaviour:
|
||||||
|
swaymsg("exec sh -c 'ne-foot &> /tmp/shim-cont-unexpected-pid'")
|
||||||
|
wait_for_window(f"u0_a{aid(0)}@machine")
|
||||||
|
machine.succeed("pkill -CONT -f 'hakurei shim'")
|
||||||
|
machine.succeed("pkill -INT -f 'hakurei -v app '")
|
||||||
|
machine.wait_until_fails("pgrep foot", timeout=5)
|
||||||
|
machine.wait_for_file("/tmp/shim-cont-unexpected-pid")
|
||||||
|
print(machine.succeed('grep "shim: got SIGCONT from unexpected process$" /tmp/shim-cont-unexpected-pid'))
|
||||||
|
|
||||||
# Start app (foot) with Wayland enablement:
|
# Start app (foot) with Wayland enablement:
|
||||||
swaymsg("exec ne-foot")
|
swaymsg("exec ne-foot")
|
||||||
wait_for_window(f"u0_a{aid(0)}@machine")
|
wait_for_window(f"u0_a{aid(0)}@machine")
|
||||||
|
|||||||
Reference in New Issue
Block a user