Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
f13dca184c
|
|||
|
3b8a3d3b00
|
|||
|
c5d24979f5
|
|||
|
1dc780bca7
|
|||
|
ec33061c92
|
|||
|
af0899de96
|
|||
|
547a2adaa4
|
|||
|
c02948e155
|
|||
|
387b86bcdd
|
|||
|
4e85643865
|
|||
|
987981df73
|
|||
|
f14e7255be
|
|||
|
a8a79a8664
|
|||
|
3ae0cec000
|
|||
|
4e518f11d8
|
|||
|
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
|
@@ -73,20 +73,20 @@ jobs:
|
||||
path: result/*
|
||||
retention-days: 1
|
||||
|
||||
planterette:
|
||||
name: Planterette
|
||||
hpkg:
|
||||
name: Hpkg
|
||||
runs-on: nix
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run NixOS test
|
||||
run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.planterette
|
||||
run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.hpkg
|
||||
|
||||
- name: Upload test output
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "planterette-vm-output"
|
||||
name: "hpkg-vm-output"
|
||||
path: result/*
|
||||
retention-days: 1
|
||||
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
- race
|
||||
- sandbox
|
||||
- sandbox-race
|
||||
- planterette
|
||||
- hpkg
|
||||
runs-on: nix
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -8,11 +8,16 @@
|
||||
|
||||
<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://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://hakurei.app"><img src="https://img.shields.io/website?url=https%3A%2F%2Fhakurei.app" alt="Website" /></a>
|
||||
</p>
|
||||
|
||||
Hakurei is a tool for running sandboxed graphical applications as dedicated subordinate users on the Linux kernel.
|
||||
It also implements [planterette (WIP)](cmd/planterette), a self-contained Android-like package manager with modern security features.
|
||||
It implements the application container of [planterette (WIP)](https://git.gensokyo.uk/security/planterette),
|
||||
a self-contained Android-like package manager with modern security features.
|
||||
|
||||
## NixOS Module usage
|
||||
|
||||
|
||||
@@ -13,12 +13,11 @@ import (
|
||||
"syscall"
|
||||
"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/hst"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/app"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/hlog"
|
||||
"hakurei.app/system"
|
||||
"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(&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 {
|
||||
if len(args) < 1 {
|
||||
@@ -244,14 +243,14 @@ func runApp(config *hst.Config) {
|
||||
ctx, stop := signal.NotifyContext(context.Background(),
|
||||
syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop() // unreachable
|
||||
a := instance.MustNew(instance.ISetuid, ctx, std)
|
||||
a := app.MustNew(ctx, std)
|
||||
|
||||
rs := new(app.RunState)
|
||||
if sa, err := a.Seal(config); err != nil {
|
||||
hlog.PrintBaseError(err, "cannot seal app:")
|
||||
internal.Exit(1)
|
||||
} else {
|
||||
internal.Exit(instance.PrintRunStateErr(instance.ISetuid, rs, sa.Run(rs)))
|
||||
internal.Exit(app.PrintRunStateErr(rs, sa.Run(rs)))
|
||||
}
|
||||
|
||||
*(*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"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/cmd/hakurei/internal/state"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"hakurei.app/cmd/hakurei/internal/state"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/hlog"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
@@ -104,6 +104,10 @@ func printShowInstance(
|
||||
}
|
||||
t.Printf(" Flags:\t%s\n", strings.Join(flags, " "))
|
||||
|
||||
if container.AutoRoot != "" {
|
||||
t.Printf(" Root:\t%s (%d)\n", container.AutoRoot, container.RootFlags)
|
||||
}
|
||||
|
||||
etc := container.Etc
|
||||
if etc == "" {
|
||||
etc = "/etc"
|
||||
|
||||
@@ -5,14 +5,13 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hakurei.app/cmd/hakurei/internal/app"
|
||||
"hakurei.app/cmd/hakurei/internal/state"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
var (
|
||||
testID = app.ID{
|
||||
testID = state.ID{
|
||||
0x8e, 0x2c, 0x76, 0xb0,
|
||||
0x66, 0xda, 0xbe, 0x57,
|
||||
0x4c, 0xf0, 0x73, 0xbd,
|
||||
@@ -43,6 +42,7 @@ func Test_printShowInstance(t *testing.T) {
|
||||
Data: /var/lib/hakurei/u0/org.chromium.Chromium
|
||||
Hostname: localhost
|
||||
Flags: userns devel net device tty mapuid autoetc
|
||||
Root: /var/lib/hakurei/base/org.debian (2)
|
||||
Etc: /etc
|
||||
Cover: /var/run/nscd
|
||||
Path: /run/current-system/sw/bin/chromium
|
||||
@@ -122,6 +122,7 @@ App
|
||||
Data: /var/lib/hakurei/u0/org.chromium.Chromium
|
||||
Hostname: localhost
|
||||
Flags: userns devel net device tty mapuid autoetc
|
||||
Root: /var/lib/hakurei/base/org.debian (2)
|
||||
Etc: /etc
|
||||
Cover: /var/run/nscd
|
||||
Path: /run/current-system/sw/bin/chromium
|
||||
@@ -257,8 +258,10 @@ App
|
||||
],
|
||||
"container": {
|
||||
"hostname": "localhost",
|
||||
"wait_delay": -1,
|
||||
"seccomp_flags": 1,
|
||||
"seccomp_presets": 1,
|
||||
"seccomp_compat": true,
|
||||
"devel": true,
|
||||
"userns": true,
|
||||
"net": true,
|
||||
@@ -301,6 +304,8 @@ App
|
||||
"/run/user/150"
|
||||
]
|
||||
],
|
||||
"auto_root": "/var/lib/hakurei/base/org.debian",
|
||||
"root_flags": 2,
|
||||
"etc": "/etc",
|
||||
"auto_etc": true,
|
||||
"cover": [
|
||||
@@ -383,8 +388,10 @@ App
|
||||
],
|
||||
"container": {
|
||||
"hostname": "localhost",
|
||||
"wait_delay": -1,
|
||||
"seccomp_flags": 1,
|
||||
"seccomp_presets": 1,
|
||||
"seccomp_compat": true,
|
||||
"devel": true,
|
||||
"userns": true,
|
||||
"net": true,
|
||||
@@ -427,6 +434,8 @@ App
|
||||
"/run/user/150"
|
||||
]
|
||||
],
|
||||
"auto_root": "/var/lib/hakurei/base/org.debian",
|
||||
"root_flags": 2,
|
||||
"etc": "/etc",
|
||||
"auto_etc": true,
|
||||
"cover": [
|
||||
@@ -460,7 +469,7 @@ func Test_printPs(t *testing.T) {
|
||||
{"no entries", make(state.Entries), false, false, " Instance PID Application Uptime\n"},
|
||||
{"no entries short", make(state.Entries), true, false, ""},
|
||||
{"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
|
||||
8e2c76b0 256 0 (app.hakurei.8e2c76b0) 1h2m32s
|
||||
@@ -563,8 +572,10 @@ func Test_printPs(t *testing.T) {
|
||||
],
|
||||
"container": {
|
||||
"hostname": "localhost",
|
||||
"wait_delay": -1,
|
||||
"seccomp_flags": 1,
|
||||
"seccomp_presets": 1,
|
||||
"seccomp_compat": true,
|
||||
"devel": true,
|
||||
"userns": true,
|
||||
"net": true,
|
||||
@@ -607,6 +618,8 @@ func Test_printPs(t *testing.T) {
|
||||
"/run/user/150"
|
||||
]
|
||||
],
|
||||
"auto_root": "/var/lib/hakurei/base/org.debian",
|
||||
"root_flags": 2,
|
||||
"etc": "/etc",
|
||||
"auto_etc": true,
|
||||
"cover": [
|
||||
|
||||
@@ -215,15 +215,14 @@ stdenv.mkDerivation {
|
||||
# create binary cache
|
||||
closureInfo="${
|
||||
closureInfo {
|
||||
rootPaths =
|
||||
[
|
||||
homeManagerConfiguration.activationPackage
|
||||
launcher
|
||||
]
|
||||
++ optionals gpu [
|
||||
mesaWrappers
|
||||
nixGL
|
||||
];
|
||||
rootPaths = [
|
||||
homeManagerConfiguration.activationPackage
|
||||
launcher
|
||||
]
|
||||
++ optionals gpu [
|
||||
mesaWrappers
|
||||
nixGL
|
||||
];
|
||||
}
|
||||
}"
|
||||
echo "copying application paths..."
|
||||
@@ -23,7 +23,7 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
hlog.Prepare("planterette")
|
||||
hlog.Prepare("hpkg")
|
||||
if err := os.Setenv("SHELL", shellPath); err != nil {
|
||||
log.Fatalf("cannot set $SHELL: %v", err)
|
||||
}
|
||||
@@ -42,7 +42,7 @@ func main() {
|
||||
flagVerbose bool
|
||||
flagDropShell bool
|
||||
)
|
||||
c := command.New(os.Stderr, log.Printf, "planterette", func([]string) error { internal.InstallOutput(flagVerbose); return nil }).
|
||||
c := command.New(os.Stderr, log.Printf, "hpkg", func([]string) error { internal.InstallOutput(flagVerbose); return nil }).
|
||||
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
|
||||
Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next hakurei action")
|
||||
|
||||
@@ -66,7 +66,7 @@ func main() {
|
||||
}
|
||||
|
||||
/*
|
||||
Look up paths to programs started by planterette.
|
||||
Look up paths to programs started by hpkg.
|
||||
This is done here to ease error handling as cleanup is not yet required.
|
||||
*/
|
||||
|
||||
@@ -82,7 +82,7 @@ func main() {
|
||||
*/
|
||||
|
||||
var workDir string
|
||||
if p, err := os.MkdirTemp("", "planterette.*"); err != nil {
|
||||
if p, err := os.MkdirTemp("", "hpkg.*"); err != nil {
|
||||
log.Printf("cannot create temporary directory: %v", err)
|
||||
return err
|
||||
} else {
|
||||
@@ -9,7 +9,7 @@ let
|
||||
buildPackage = self.buildPackage.${system};
|
||||
in
|
||||
nixosTest {
|
||||
name = "planterette";
|
||||
name = "hpkg";
|
||||
nodes.machine = {
|
||||
environment.etc = {
|
||||
"foot.pkg".source = callPackage ./foot.nix { inherit buildPackage; };
|
||||
@@ -79,15 +79,15 @@ print(machine.succeed("sudo -u alice -i hakurei version"))
|
||||
machine.wait_for_file("/run/user/1000/wayland-1")
|
||||
machine.wait_for_file("/tmp/sway-ipc.sock")
|
||||
|
||||
# Prepare planterette directory:
|
||||
# Prepare hpkg directory:
|
||||
machine.succeed("install -dm 0700 -o alice -g users /var/lib/hakurei/1000")
|
||||
|
||||
# Install planterette app:
|
||||
swaymsg("exec planterette -v install /etc/foot.pkg && touch /tmp/planterette-install-ok")
|
||||
machine.wait_for_file("/tmp/planterette-install-ok")
|
||||
# Install hpkg app:
|
||||
swaymsg("exec hpkg -v install /etc/foot.pkg && touch /tmp/hpkg-install-ok")
|
||||
machine.wait_for_file("/tmp/hpkg-install-ok")
|
||||
|
||||
# Start app (foot) with Wayland enablement:
|
||||
swaymsg("exec planterette -v start org.codeberg.dnkl.foot")
|
||||
swaymsg("exec hpkg -v start org.codeberg.dnkl.foot")
|
||||
wait_for_window("hakurei@machine-foot")
|
||||
machine.send_chars("clear; wayland-info && touch /tmp/success-client\n")
|
||||
machine.wait_for_file("/tmp/hakurei.1000/tmpdir/2/success-client")
|
||||
65
container/autoetc.go
Normal file
65
container/autoetc.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func init() { gob.Register(new(AutoEtcOp)) }
|
||||
|
||||
// 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.
|
||||
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 }
|
||||
|
||||
func (e *AutoEtcOp) early(*Params) error { return nil }
|
||||
func (e *AutoEtcOp) apply(*Params) error {
|
||||
const target = sysrootPath + "/etc/"
|
||||
rel := e.hostRel() + "/"
|
||||
|
||||
if err := os.MkdirAll(target, 0755); err != nil {
|
||||
return wrapErrSelf(err)
|
||||
}
|
||||
if d, err := os.ReadDir(toSysroot(e.hostPath())); err != nil {
|
||||
return wrapErrSelf(err)
|
||||
} else {
|
||||
for _, ent := range d {
|
||||
n := ent.Name()
|
||||
switch n {
|
||||
case ".host":
|
||||
|
||||
case "passwd":
|
||||
case "group":
|
||||
|
||||
case "mtab":
|
||||
if err = os.Symlink("/proc/mounts", target+n); err != nil {
|
||||
return wrapErrSelf(err)
|
||||
}
|
||||
|
||||
default:
|
||||
if err = os.Symlink(rel+n, target+n); err != nil {
|
||||
return wrapErrSelf(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (e *AutoEtcOp) hostPath() string { return "/etc/" + e.hostRel() }
|
||||
func (e *AutoEtcOp) hostRel() string { return ".host/" + e.Prefix }
|
||||
|
||||
func (e *AutoEtcOp) Is(op Op) bool {
|
||||
ve, ok := op.(*AutoEtcOp)
|
||||
return ok && ((e == nil && ve == nil) || (e != nil && ve != nil && *e == *ve))
|
||||
}
|
||||
func (*AutoEtcOp) prefix() string { return "setting up" }
|
||||
func (e *AutoEtcOp) String() string { return fmt.Sprintf("auto etc %s", e.Prefix) }
|
||||
91
container/autoroot.go
Normal file
91
container/autoroot.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
. "syscall"
|
||||
)
|
||||
|
||||
func init() { gob.Register(new(AutoRootOp)) }
|
||||
|
||||
// Root appends an [Op] that expands a directory into a toplevel bind mount mirror on container root.
|
||||
// This is not a generic setup op. It is implemented here to reduce ipc overhead.
|
||||
func (f *Ops) Root(host, prefix string, flags int) *Ops {
|
||||
*f = append(*f, &AutoRootOp{host, prefix, flags, nil})
|
||||
return f
|
||||
}
|
||||
|
||||
type AutoRootOp struct {
|
||||
Host, Prefix string
|
||||
// passed through to bindMount
|
||||
Flags int
|
||||
|
||||
// obtained during early;
|
||||
// these wrap the underlying Op because BindMountOp is relatively complex,
|
||||
// so duplicating that code would be unwise
|
||||
resolved []Op
|
||||
}
|
||||
|
||||
func (r *AutoRootOp) early(params *Params) error {
|
||||
if !path.IsAbs(r.Host) {
|
||||
return msg.WrapErr(EBADE, fmt.Sprintf("path %q is not absolute", r.Host))
|
||||
}
|
||||
|
||||
if d, err := os.ReadDir(r.Host); err != nil {
|
||||
return wrapErrSelf(err)
|
||||
} else {
|
||||
r.resolved = make([]Op, 0, len(d))
|
||||
for _, ent := range d {
|
||||
name := ent.Name()
|
||||
if IsAutoRootBindable(name) {
|
||||
op := &BindMountOp{
|
||||
Source: path.Join(r.Host, name),
|
||||
Target: "/" + name,
|
||||
Flags: r.Flags,
|
||||
}
|
||||
if err = op.early(params); err != nil {
|
||||
return err
|
||||
}
|
||||
r.resolved = append(r.resolved, op)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *AutoRootOp) apply(params *Params) error {
|
||||
for _, op := range r.resolved {
|
||||
msg.Verbosef("%s %s", op.prefix(), op)
|
||||
if err := op.apply(params); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *AutoRootOp) Is(op Op) bool {
|
||||
vr, ok := op.(*AutoRootOp)
|
||||
return ok && ((r == nil && vr == nil) || (r != nil && vr != nil &&
|
||||
r.Host == vr.Host && r.Prefix == vr.Prefix && r.Flags == vr.Flags))
|
||||
}
|
||||
func (*AutoRootOp) prefix() string { return "setting up" }
|
||||
func (r *AutoRootOp) String() string {
|
||||
return fmt.Sprintf("auto root %q prefix %s flags %#x", r.Host, r.Prefix, r.Flags)
|
||||
}
|
||||
|
||||
// IsAutoRootBindable returns whether a dir entry name is selected for AutoRoot.
|
||||
func IsAutoRootBindable(name string) bool {
|
||||
switch name {
|
||||
case "proc":
|
||||
case "dev":
|
||||
case "tmp":
|
||||
case "mnt":
|
||||
case "etc":
|
||||
|
||||
default:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
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"
|
||||
)
|
||||
|
||||
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 (
|
||||
// Container represents a container environment being prepared or run.
|
||||
// None of [Container] methods are safe for concurrent use.
|
||||
@@ -29,9 +39,6 @@ type (
|
||||
// with behaviour identical to its [exec.Cmd] counterpart.
|
||||
ExtraFiles []*os.File
|
||||
|
||||
// Custom [exec.Cmd] initialisation function.
|
||||
CommandContext func(ctx context.Context) (cmd *exec.Cmd)
|
||||
|
||||
// param encoder for shim and init
|
||||
setup *gob.Encoder
|
||||
// cancels cmd
|
||||
@@ -59,6 +66,10 @@ type (
|
||||
Path string
|
||||
// Initial process argv.
|
||||
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.
|
||||
Uid int
|
||||
@@ -68,6 +79,7 @@ type (
|
||||
Hostname string
|
||||
// Sequential container setup ops.
|
||||
*Ops
|
||||
|
||||
// Seccomp system call filter rules.
|
||||
SeccompRules []seccomp.NativeRule
|
||||
// Extra seccomp flags.
|
||||
@@ -76,6 +88,7 @@ type (
|
||||
SeccompPresets seccomp.FilterPreset
|
||||
// Do not load seccomp program.
|
||||
SeccompDisable bool
|
||||
|
||||
// Permission bits of newly created parent directories.
|
||||
// The zero value is interpreted as 0755.
|
||||
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 {
|
||||
if p.cmd != nil {
|
||||
return errors.New("sandbox: already started")
|
||||
return errors.New("container: already started")
|
||||
}
|
||||
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)
|
||||
@@ -116,19 +130,22 @@ func (p *Container) Start() error {
|
||||
p.SeccompPresets |= seccomp.PresetDenyTTY
|
||||
}
|
||||
|
||||
if p.CommandContext != nil {
|
||||
p.cmd = p.CommandContext(ctx)
|
||||
} else {
|
||||
p.cmd = exec.CommandContext(ctx, MustExecutable())
|
||||
p.cmd.Args = []string{"init"}
|
||||
if p.AdoptWaitDelay == 0 {
|
||||
p.AdoptWaitDelay = 5 * time.Second
|
||||
}
|
||||
// to allow disabling this behaviour
|
||||
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.WaitDelay = p.WaitDelay
|
||||
if p.Cancel != nil {
|
||||
p.cmd.Cancel = func() error { return p.Cancel(p.cmd) }
|
||||
} 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.SysProcAttr = &SysProcAttr{
|
||||
@@ -162,6 +179,8 @@ func (p *Container) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Serve serves [Container.Params] to the container init.
|
||||
// Serve must only be called once.
|
||||
func (p *Container) Serve() error {
|
||||
if p.setup == nil {
|
||||
panic("invalid serve")
|
||||
@@ -215,6 +234,7 @@ func (p *Container) Serve() error {
|
||||
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) String() string {
|
||||
@@ -222,6 +242,14 @@ func (p *Container) String() string {
|
||||
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 {
|
||||
return &Container{name: name, ctx: ctx,
|
||||
Params: Params{Args: append([]string{name}, args...), Dir: "/", Ops: new(Ops)},
|
||||
|
||||
@@ -4,28 +4,88 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hakurei.app/command"
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/container/vfs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/hlog"
|
||||
"hakurei.app/ldd"
|
||||
)
|
||||
|
||||
const (
|
||||
ignore = "\x00"
|
||||
ignoreV = -1
|
||||
|
||||
pathPrefix = "/etc/hakurei/"
|
||||
pathWantMnt = pathPrefix + "want-mnt"
|
||||
pathReadonly = pathPrefix + "readonly"
|
||||
)
|
||||
|
||||
var containerTestCases = []struct {
|
||||
name string
|
||||
filter bool
|
||||
session bool
|
||||
net bool
|
||||
ro bool
|
||||
ops *container.Ops
|
||||
|
||||
mnt []*vfs.MountInfoEntry
|
||||
uid int
|
||||
gid int
|
||||
|
||||
rules []seccomp.NativeRule
|
||||
flags seccomp.ExportFlag
|
||||
presets seccomp.FilterPreset
|
||||
}{
|
||||
{"minimal", true, false, false, true,
|
||||
new(container.Ops), nil,
|
||||
1000, 100, nil, 0, seccomp.PresetStrict},
|
||||
{"allow", true, true, true, false,
|
||||
new(container.Ops), nil,
|
||||
1000, 100, nil, 0, seccomp.PresetExt | seccomp.PresetDenyDevel},
|
||||
{"no filter", false, true, true, true,
|
||||
new(container.Ops), nil,
|
||||
1000, 100, nil, 0, seccomp.PresetExt},
|
||||
{"custom rules", true, true, true, false,
|
||||
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, true,
|
||||
new(container.Ops).
|
||||
Tmpfs(hst.Tmp, 0, 0755),
|
||||
[]*vfs.MountInfoEntry{
|
||||
ent("/", hst.Tmp, "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", ignore),
|
||||
},
|
||||
9, 9, nil, 0, seccomp.PresetStrict},
|
||||
{"dev", true, true /* go test output is not a tty */, false, 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) {
|
||||
{
|
||||
oldVerbose := hlog.Load()
|
||||
@@ -35,124 +95,93 @@ func TestContainer(t *testing.T) {
|
||||
t.Cleanup(func() { container.SetOutput(oldOutput) })
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
filter bool
|
||||
session bool
|
||||
net bool
|
||||
ops *container.Ops
|
||||
mnt []*vfs.MountInfoEntry
|
||||
host string
|
||||
rules []seccomp.NativeRule
|
||||
flags seccomp.ExportFlag
|
||||
presets seccomp.FilterPreset
|
||||
}{
|
||||
{"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},
|
||||
}
|
||||
t.Run("cancel", testContainerCancel(nil, func(t *testing.T, c *container.Container) {
|
||||
wantErr := context.Canceled
|
||||
wantExitCode := 0
|
||||
if err := c.Wait(); !errors.Is(err, wantErr) {
|
||||
hlog.PrintBaseError(err, "wait:")
|
||||
t.Errorf("Wait: error = %v, want %v", err, wantErr)
|
||||
}
|
||||
if ps := c.ProcessState(); ps == nil {
|
||||
t.Errorf("ProcessState unexpectedly returned nil")
|
||||
} else if code := ps.ExitCode(); code != wantExitCode {
|
||||
t.Errorf("ExitCode: %d, want %d", code, wantExitCode)
|
||||
}
|
||||
}))
|
||||
|
||||
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) {
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
|
||||
ctx, cancel := context.WithTimeout(t.Context(), helperDefaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
c := container.New(ctx, "/usr/bin/sandbox.test", "-test.v",
|
||||
"-test.run=TestHelperCheckContainer", "--", "check", tc.host)
|
||||
c.Uid = 1000
|
||||
c.Gid = 100
|
||||
c.Hostname = tc.host
|
||||
c.CommandContext = commandContext
|
||||
var libPaths []string
|
||||
c := helperNewContainerLibPaths(ctx, &libPaths, "container", strconv.Itoa(i))
|
||||
c.Uid = tc.uid
|
||||
c.Gid = tc.gid
|
||||
c.Hostname = hostnameFromTestCase(tc.name)
|
||||
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.SeccompFlags = tc.flags | seccomp.AllowMultiarch
|
||||
c.SeccompPresets = tc.presets
|
||||
c.SeccompDisable = !tc.filter
|
||||
c.RetainSession = tc.session
|
||||
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.
|
||||
Readonly(pathReadonly, 0755).
|
||||
Tmpfs("/tmp", 0, 0755).
|
||||
Bind(os.Args[0], os.Args[0], 0).
|
||||
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)
|
||||
}
|
||||
Place("/etc/hostname", []byte(c.Hostname))
|
||||
// needs /proc to check mountinfo
|
||||
c.Proc("/proc")
|
||||
|
||||
// mountinfo cannot be resolved directly by helper due to libPaths nondeterminism
|
||||
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,
|
||||
e("/", "/tmp", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
|
||||
e(ignore, os.Args[0], "ro,nosuid,nodev,relatime", ignore, ignore, ignore),
|
||||
e(ignore, "/etc/hostname", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", ignore),
|
||||
ent("/sysroot", "/", "rw,nosuid,nodev,relatime", "tmpfs", "rootfs", ignore),
|
||||
// Bind(os.Args[0], helperInnerPath, 0)
|
||||
ent(ignore, helperInnerPath, "ro,nosuid,nodev,relatime", ignore, ignore, ignore),
|
||||
)
|
||||
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,
|
||||
// Readonly(pathReadonly, 0755)
|
||||
ent("/", pathReadonly, "ro,nosuid,nodev", "tmpfs", "readonly", ignore),
|
||||
// Tmpfs("/tmp", 0, 0755)
|
||||
ent("/", "/tmp", "rw,nosuid,nodev,relatime", "tmpfs", "ephemeral", 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)
|
||||
if err := gob.NewEncoder(want).Encode(mnt); err != nil {
|
||||
t.Fatalf("cannot serialise expected mount points: %v", err)
|
||||
}
|
||||
c.Stdin = want
|
||||
c.Place(pathWantMnt, want.Bytes())
|
||||
|
||||
if tc.ro {
|
||||
c.Remount("/", syscall.MS_RDONLY)
|
||||
}
|
||||
|
||||
if err := c.Start(); err != nil {
|
||||
hlog.PrintBaseError(err, "start:")
|
||||
@@ -169,7 +198,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{
|
||||
ID: ignoreV,
|
||||
Parent: ignoreV,
|
||||
@@ -184,6 +213,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) {
|
||||
c := container.New(t.Context(), "ldd", "/usr/bin/env")
|
||||
c.SeccompFlags |= seccomp.AllowMultiarch
|
||||
@@ -197,85 +270,118 @@ func TestContainerString(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
const (
|
||||
blockExitCodeInterrupt = 2
|
||||
)
|
||||
|
||||
func TestHelperCheckContainer(t *testing.T) {
|
||||
if len(os.Args) != 6 || os.Args[4] != "check" {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
func init() {
|
||||
helperCommands = append(helperCommands, func(c command.Command) {
|
||||
c.Command("block", command.UsageInternal, func(args []string) error {
|
||||
if _, err := os.NewFile(3, "sync").Write([]byte{0}); err != nil {
|
||||
return fmt.Errorf("write to sync pipe: %v", err)
|
||||
}
|
||||
{
|
||||
sig := make(chan os.Signal, 1)
|
||||
signal.Notify(sig, os.Interrupt)
|
||||
go func() { <-sig; os.Exit(blockExitCodeInterrupt) }()
|
||||
}
|
||||
select {}
|
||||
})
|
||||
|
||||
// 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") {
|
||||
t.Errorf("[FAIL] %s", cur)
|
||||
c.Command("container", command.UsageInternal, func(args []string) error {
|
||||
if len(args) != 1 {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
tc := containerTestCases[0]
|
||||
if i, err := strconv.Atoi(args[0]); err != nil {
|
||||
return fmt.Errorf("cannot parse test case index: %v", err)
|
||||
} else {
|
||||
t.Logf("[ OK ] %s", cur)
|
||||
tc = containerTestCases[i]
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
if err := d.Err(); err != nil {
|
||||
t.Errorf("cannot parse mountinfo: %v", err)
|
||||
}
|
||||
if uid := syscall.Getuid(); uid != tc.uid {
|
||||
return fmt.Errorf("uid: %d, want %d", uid, tc.uid)
|
||||
}
|
||||
if gid := syscall.Getgid(); gid != tc.gid {
|
||||
return fmt.Errorf("gid: %d, want %d", gid, tc.gid)
|
||||
}
|
||||
|
||||
if i != len(mnt) {
|
||||
t.Errorf("got %d entries, want %d", i, len(mnt))
|
||||
}
|
||||
wantHost := hostnameFromTestCase(tc.name)
|
||||
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)
|
||||
}
|
||||
|
||||
if _, err := os.Create(pathReadonly + "/nonexistent"); !errors.Is(err, syscall.EROFS) {
|
||||
return err
|
||||
}
|
||||
|
||||
{
|
||||
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)
|
||||
}
|
||||
|
||||
if tc.ro && len(mnt) > 0 {
|
||||
// Remount("/", syscall.MS_RDONLY)
|
||||
mnt[0].VfsOptstr = "ro,nosuid,nodev"
|
||||
}
|
||||
|
||||
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 (
|
||||
// time to wait for linger processes after death of initial process
|
||||
residualProcessTimeout = 5 * time.Second
|
||||
/* intermediate tmpfs mount point
|
||||
|
||||
// intermediate tmpfs mount point
|
||||
basePath = "/tmp"
|
||||
this path might seem like a weird choice, however there are many good reasons to use it:
|
||||
- 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
|
||||
setupEnv = "HAKUREI_SETUP"
|
||||
@@ -111,6 +121,10 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
||||
log.Fatalf("cannot make / rslave: %v", err)
|
||||
}
|
||||
|
||||
/* early is called right before pivot_root into intermediate root;
|
||||
this step is mostly for gathering information that would otherwise be difficult to obtain
|
||||
via library functions after pivot_root, and implementations are expected to avoid changing
|
||||
the state of the mount namespace */
|
||||
for i, op := range *params.Ops {
|
||||
if op == nil {
|
||||
log.Fatalf("invalid op %d", i)
|
||||
@@ -124,10 +138,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)
|
||||
}
|
||||
if err := os.Chdir(basePath); err != nil {
|
||||
if err := os.Chdir(intermediateHostPath); err != nil {
|
||||
log.Fatalf("cannot enter base path: %v", err)
|
||||
}
|
||||
|
||||
@@ -141,14 +155,18 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
||||
if err := os.Mkdir(hostDir, 0755); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
// pivot_root uncovers basePath in hostDir
|
||||
if err := PivotRoot(basePath, hostDir); err != nil {
|
||||
// pivot_root uncovers intermediateHostPath in hostDir
|
||||
if err := PivotRoot(intermediateHostPath, hostDir); err != nil {
|
||||
log.Fatalf("cannot pivot into intermediate root: %v", err)
|
||||
}
|
||||
if err := os.Chdir("/"); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
/* apply is called right after pivot_root and entering the new root;
|
||||
this step sets up the container filesystem, and implementations are expected to keep the host root
|
||||
and sysroot mount points intact but otherwise can do whatever they need to;
|
||||
chdir is allowed but discouraged */
|
||||
for i, op := range *params.Ops {
|
||||
// ops already checked during early setup
|
||||
msg.Verbosef("%s %s", op.prefix(), op)
|
||||
@@ -198,7 +216,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)
|
||||
}
|
||||
|
||||
@@ -257,13 +275,14 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
||||
cmd.ExtraFiles = extraFiles
|
||||
cmd.Dir = params.Dir
|
||||
|
||||
msg.Verbosef("starting initial program %s", params.Path)
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
msg.Suspend()
|
||||
|
||||
if err := closeSetup(); err != nil {
|
||||
log.Println("cannot close setup pipe:", err)
|
||||
log.Printf("cannot close setup pipe: %v", err)
|
||||
// not fatal
|
||||
}
|
||||
|
||||
@@ -297,7 +316,7 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
||||
}
|
||||
}
|
||||
if !errors.Is(err, ECHILD) {
|
||||
log.Println("unexpected wait4 response:", err)
|
||||
log.Printf("unexpected wait4 response: %v", err)
|
||||
}
|
||||
|
||||
close(done)
|
||||
@@ -305,7 +324,7 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
||||
|
||||
// handle signals to dump withheld messages
|
||||
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
|
||||
timeout := make(chan struct{})
|
||||
@@ -315,9 +334,16 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
||||
select {
|
||||
case s := <-sig:
|
||||
if msg.Resume() {
|
||||
msg.Verbosef("terminating on %s after process start", s.String())
|
||||
msg.Verbosef("%s after process start", s.String())
|
||||
} 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)
|
||||
case w := <-info:
|
||||
@@ -337,10 +363,7 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
||||
msg.Verbosef("initial process exited with status %#x", w.wstatus)
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(residualProcessTimeout)
|
||||
close(timeout)
|
||||
}()
|
||||
go func() { time.Sleep(params.AdoptWaitDelay); close(timeout) }()
|
||||
}
|
||||
case <-done:
|
||||
msg.BeforeExit()
|
||||
@@ -353,9 +376,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".
|
||||
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
|
||||
Init(prepare, setVerbose)
|
||||
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...)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"hakurei.app/container/vfs"
|
||||
)
|
||||
|
||||
// bindMount mounts source on target and recursively applies flags if MS_REC is set.
|
||||
func (p *procPaths) bindMount(source, target string, flags uintptr, eq bool) error {
|
||||
if eq {
|
||||
msg.Verbosef("resolved %q flags %#x", target, flags)
|
||||
@@ -22,6 +23,11 @@ func (p *procPaths) bindMount(source, target string, flags uintptr, eq bool) err
|
||||
fmt.Sprintf("cannot mount %q on %q:", source, target))
|
||||
}
|
||||
|
||||
return p.remount(target, flags)
|
||||
}
|
||||
|
||||
// remount applies flags on target, recursively if MS_REC is set.
|
||||
func (p *procPaths) remount(target string, flags uintptr) error {
|
||||
var targetFinal string
|
||||
if v, err := filepath.EvalSymlinks(target); err != nil {
|
||||
return wrapErrSelf(err)
|
||||
@@ -83,6 +89,7 @@ func (p *procPaths) bindMount(source, target string, flags uintptr, eq bool) err
|
||||
})
|
||||
}
|
||||
|
||||
// remountWithFlags remounts mount point described by [vfs.MountInfoNode].
|
||||
func remountWithFlags(n *vfs.MountInfoNode, mf uintptr) error {
|
||||
kf, unmatched := n.Flags()
|
||||
if len(unmatched) != 0 {
|
||||
@@ -97,7 +104,7 @@ func remountWithFlags(n *vfs.MountInfoNode, mf uintptr) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func mountTmpfs(fsname, name string, size int, perm os.FileMode) error {
|
||||
func mountTmpfs(fsname, name string, flags uintptr, size int, perm os.FileMode) error {
|
||||
target := toSysroot(name)
|
||||
if err := os.MkdirAll(target, parentPerm(perm)); err != nil {
|
||||
return wrapErrSelf(err)
|
||||
@@ -107,7 +114,7 @@ func mountTmpfs(fsname, name string, size int, perm os.FileMode) error {
|
||||
opt += fmt.Sprintf(",size=%d", size)
|
||||
}
|
||||
return wrapErrSuffix(
|
||||
Mount(fsname, target, "tmpfs", MS_NOSUID|MS_NODEV, opt),
|
||||
Mount(fsname, target, "tmpfs", flags, opt),
|
||||
fmt.Sprintf("cannot mount tmpfs on %q:", name))
|
||||
}
|
||||
|
||||
|
||||
210
container/ops.go
210
container/ops.go
@@ -15,7 +15,10 @@ import (
|
||||
|
||||
type (
|
||||
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(params *Params) error
|
||||
// apply is called in intermediate root.
|
||||
@@ -27,11 +30,43 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
// Grow grows the slice Ops points to using [slices.Grow].
|
||||
func (f *Ops) Grow(n int) { *f = slices.Grow(*f, n) }
|
||||
|
||||
func init() { gob.Register(new(RemountOp)) }
|
||||
|
||||
// Remount appends an [Op] that applies [RemountOp.Flags] on container path [RemountOp.Target].
|
||||
func (f *Ops) Remount(target string, flags uintptr) *Ops {
|
||||
*f = append(*f, &RemountOp{target, flags})
|
||||
return f
|
||||
}
|
||||
|
||||
type RemountOp struct {
|
||||
Target string
|
||||
Flags uintptr
|
||||
}
|
||||
|
||||
func (*RemountOp) early(*Params) error { return nil }
|
||||
func (r *RemountOp) apply(*Params) error {
|
||||
if !path.IsAbs(r.Target) {
|
||||
return msg.WrapErr(EBADE, fmt.Sprintf("path %q is not absolute", r.Target))
|
||||
}
|
||||
return wrapErrSuffix(hostProc.remount(toSysroot(r.Target), r.Flags),
|
||||
fmt.Sprintf("cannot remount %q:", r.Target))
|
||||
}
|
||||
|
||||
func (r *RemountOp) Is(op Op) bool { vr, ok := op.(*RemountOp); return ok && *r == *vr }
|
||||
func (*RemountOp) prefix() string { return "remounting" }
|
||||
func (r *RemountOp) String() string { return fmt.Sprintf("%q flags %#x", r.Target, r.Flags) }
|
||||
|
||||
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 {
|
||||
Source, SourceFinal, Target string
|
||||
|
||||
@@ -39,8 +74,11 @@ type BindMountOp struct {
|
||||
}
|
||||
|
||||
const (
|
||||
// BindOptional skips nonexistent host paths.
|
||||
BindOptional = 1 << iota
|
||||
// BindWritable mounts filesystem read-write.
|
||||
BindWritable
|
||||
// BindDevice allows access to devices (special files) on this filesystem.
|
||||
BindDevice
|
||||
)
|
||||
|
||||
@@ -106,16 +144,17 @@ func (b *BindMountOp) String() string {
|
||||
if b.Source == b.Target {
|
||||
return fmt.Sprintf("%q flags %#x", b.Source, b.Flags)
|
||||
}
|
||||
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
|
||||
return fmt.Sprintf("%q on %q flags %#x", b.Source, b.Target, b.Flags)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
func (p MountProcOp) early(*Params) error { return nil }
|
||||
@@ -137,14 +176,15 @@ func (p MountProcOp) apply(params *Params) error {
|
||||
func (p MountProcOp) Is(op Op) bool { vp, ok := op.(MountProcOp); return ok && p == vp }
|
||||
func (MountProcOp) prefix() string { return "mounting" }
|
||||
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)) }
|
||||
|
||||
// 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
|
||||
|
||||
func (d MountDevOp) early(*Params) error { return nil }
|
||||
@@ -156,7 +196,7 @@ func (d MountDevOp) apply(params *Params) error {
|
||||
}
|
||||
target := toSysroot(v)
|
||||
|
||||
if err := mountTmpfs("devtmpfs", v, 0, params.ParentPerm); err != nil {
|
||||
if err := mountTmpfs("devtmpfs", v, MS_NOSUID|MS_NODEV, 0, params.ParentPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -231,14 +271,15 @@ func (d MountDevOp) apply(params *Params) error {
|
||||
func (d MountDevOp) Is(op Op) bool { vd, ok := op.(MountDevOp); return ok && d == vd }
|
||||
func (MountDevOp) prefix() string { return "mounting" }
|
||||
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)) }
|
||||
|
||||
// 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
|
||||
|
||||
func (m MountMqueueOp) early(*Params) error { return nil }
|
||||
@@ -260,18 +301,27 @@ func (m MountMqueueOp) apply(params *Params) error {
|
||||
func (m MountMqueueOp) Is(op Op) bool { vm, ok := op.(MountMqueueOp); return ok && m == vm }
|
||||
func (MountMqueueOp) prefix() string { return "mounting" }
|
||||
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)) }
|
||||
|
||||
// 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{"ephemeral", dest, MS_NOSUID | MS_NODEV, size, perm})
|
||||
return f
|
||||
}
|
||||
|
||||
// Readonly appends an [Op] that mounts read-only tmpfs on container path [MountTmpfsOp.Path].
|
||||
func (f *Ops) Readonly(dest string, perm os.FileMode) *Ops {
|
||||
*f = append(*f, &MountTmpfsOp{"readonly", dest, MS_RDONLY | MS_NOSUID | MS_NODEV, 0, perm})
|
||||
return f
|
||||
}
|
||||
|
||||
type MountTmpfsOp struct {
|
||||
Path string
|
||||
Size int
|
||||
Perm os.FileMode
|
||||
FSName string
|
||||
Path string
|
||||
Flags uintptr
|
||||
Size int
|
||||
Perm os.FileMode
|
||||
}
|
||||
|
||||
func (t *MountTmpfsOp) early(*Params) error { return nil }
|
||||
@@ -282,20 +332,21 @@ func (t *MountTmpfsOp) apply(*Params) error {
|
||||
if t.Size < 0 || t.Size > math.MaxUint>>1 {
|
||||
return msg.WrapErr(EBADE, fmt.Sprintf("size %d out of bounds", t.Size))
|
||||
}
|
||||
return mountTmpfs("tmpfs", t.Path, t.Size, t.Perm)
|
||||
return mountTmpfs(t.FSName, t.Path, t.Flags, t.Size, t.Perm)
|
||||
}
|
||||
|
||||
func (t *MountTmpfsOp) Is(op Op) bool { vt, ok := op.(*MountTmpfsOp); return ok && *t == *vt }
|
||||
func (*MountTmpfsOp) prefix() string { return "mounting" }
|
||||
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)) }
|
||||
|
||||
// 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
|
||||
|
||||
func (l *SymlinkOp) early(*Params) error {
|
||||
@@ -331,14 +382,15 @@ func (l *SymlinkOp) apply(params *Params) error {
|
||||
func (l *SymlinkOp) Is(op Op) bool { vl, ok := op.(*SymlinkOp); return ok && *l == *vl }
|
||||
func (*SymlinkOp) prefix() string { return "creating" }
|
||||
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)) }
|
||||
|
||||
// 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 {
|
||||
Path string
|
||||
Perm os.FileMode
|
||||
@@ -359,14 +411,21 @@ func (m *MkdirOp) apply(*Params) error {
|
||||
func (m *MkdirOp) Is(op Op) bool { vm, ok := op.(*MkdirOp); return ok && m == vm }
|
||||
func (*MkdirOp) prefix() string { return "creating" }
|
||||
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)) }
|
||||
|
||||
// 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 {
|
||||
Path string
|
||||
Data []byte
|
||||
@@ -415,68 +474,3 @@ func (*TmpfileOp) prefix() string { return "placing" }
|
||||
func (t *TmpfileOp) String() string {
|
||||
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)) }
|
||||
|
||||
// AutoEtcOp 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.
|
||||
type AutoEtcOp struct{ Prefix string }
|
||||
|
||||
func (e *AutoEtcOp) early(*Params) error { return nil }
|
||||
func (e *AutoEtcOp) apply(*Params) error {
|
||||
const target = sysrootPath + "/etc/"
|
||||
rel := e.hostRel() + "/"
|
||||
|
||||
if err := os.MkdirAll(target, 0755); err != nil {
|
||||
return wrapErrSelf(err)
|
||||
}
|
||||
if d, err := os.ReadDir(toSysroot(e.hostPath())); err != nil {
|
||||
return wrapErrSelf(err)
|
||||
} else {
|
||||
for _, ent := range d {
|
||||
n := ent.Name()
|
||||
switch n {
|
||||
case ".host":
|
||||
|
||||
case "passwd":
|
||||
case "group":
|
||||
|
||||
case "mtab":
|
||||
if err = os.Symlink("/proc/mounts", target+n); err != nil {
|
||||
return wrapErrSelf(err)
|
||||
}
|
||||
|
||||
default:
|
||||
if err = os.Symlink(rel+n, target+n); err != nil {
|
||||
return wrapErrSelf(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (e *AutoEtcOp) hostPath() string { return "/etc/" + e.hostRel() }
|
||||
func (e *AutoEtcOp) hostRel() string { return ".host/" + e.Prefix }
|
||||
|
||||
func (e *AutoEtcOp) Is(op Op) bool {
|
||||
ve, ok := op.(*AutoEtcOp)
|
||||
return ok && ((e == nil && ve == nil) || (e != nil && ve != nil && *e == *ve))
|
||||
}
|
||||
func (*AutoEtcOp) prefix() string { return "setting up" }
|
||||
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
|
||||
|
||||
#include <libseccomp-helper.h>
|
||||
#include <sys/personality.h>
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
@@ -14,6 +15,11 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
PER_LINUX = C.PER_LINUX
|
||||
PER_LINUX32 = C.PER_LINUX32
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidRules = errors.New("invalid native rules slice")
|
||||
)
|
||||
|
||||
@@ -14,81 +14,28 @@ import (
|
||||
func TestExport(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
presets FilterPreset
|
||||
flags ExportFlag
|
||||
want []byte
|
||||
presets FilterPreset
|
||||
wantErr bool
|
||||
}{
|
||||
{"compat", 0, 0, []byte{
|
||||
0x95, 0xec, 0x69, 0xd0, 0x17, 0x73, 0x3e, 0x07,
|
||||
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 |
|
||||
{"everything", AllowMultiarch | AllowCAN |
|
||||
AllowBluetooth, PresetExt |
|
||||
PresetDenyNS | PresetDenyTTY | PresetDenyDevel |
|
||||
PresetLinux32, AllowMultiarch | AllowCAN |
|
||||
AllowBluetooth, []byte{
|
||||
0xe9, 0x9d, 0xd3, 0x45, 0xe1, 0x95, 0x41, 0x34,
|
||||
0x73, 0xd3, 0xcb, 0xee, 0x07, 0xb4, 0xed, 0x57,
|
||||
0xb9, 0x08, 0xbf, 0xa8, 0x9e, 0xa2, 0x07, 0x2f,
|
||||
0xe9, 0x34, 0x82, 0x84, 0x7f, 0x50, 0xb5, 0xb7,
|
||||
0x58, 0xda, 0x17, 0xe7, 0x4c, 0xa2, 0xbb, 0xc0,
|
||||
0x08, 0x13, 0xde, 0x49, 0xa2, 0xb9, 0xbf, 0x83,
|
||||
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},
|
||||
PresetLinux32, false},
|
||||
|
||||
{"compat", 0, 0, false},
|
||||
{"base", 0, PresetExt, false},
|
||||
{"strict", 0, PresetStrict, false},
|
||||
{"strict compat", 0, PresetDenyNS | PresetDenyTTY | PresetDenyDevel, false},
|
||||
{"hakurei default", 0, PresetExt | PresetDenyDevel, false},
|
||||
{"hakurei tty", 0, PresetExt | PresetDenyNS | PresetDenyDevel, false},
|
||||
}
|
||||
|
||||
buf := make([]byte, 8)
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
e := New(Preset(tc.presets, tc.flags), tc.flags)
|
||||
want := bpfExpected[bpfPreset{tc.flags, tc.presets}]
|
||||
digest := sha512.New()
|
||||
|
||||
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 {
|
||||
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",
|
||||
got, tc.want)
|
||||
got, want)
|
||||
return
|
||||
}
|
||||
})
|
||||
@@ -132,7 +79,7 @@ func TestExport(t *testing.T) {
|
||||
|
||||
func BenchmarkExport(b *testing.B) {
|
||||
buf := make([]byte, 8)
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
e := New(
|
||||
Preset(PresetExt|PresetDenyNS|PresetDenyTTY|PresetDenyDevel|PresetLinux32,
|
||||
AllowMultiarch|AllowCAN|AllowBluetooth),
|
||||
|
||||
@@ -4,8 +4,14 @@
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
use strict;
|
||||
use POSIX ();
|
||||
|
||||
my $command = "mksysnum_linux.pl ". join(' ', @ARGV);
|
||||
my $uname_arch = (POSIX::uname)[4];
|
||||
my %syscall_cutoff_arch = (
|
||||
"x86_64" => 302,
|
||||
"aarch64" => 281,
|
||||
);
|
||||
|
||||
print <<EOF;
|
||||
// $command
|
||||
@@ -30,7 +36,7 @@ sub fmt {
|
||||
}
|
||||
(my $name_upper = $name) =~ y/a-z/A-Z/;
|
||||
$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){
|
||||
print " \"$name\": SYS_$name_upper,\n";
|
||||
}
|
||||
|
||||
@@ -4,15 +4,9 @@ package seccomp
|
||||
#cgo linux pkg-config: --static libseccomp
|
||||
|
||||
#include <seccomp.h>
|
||||
#include <sys/personality.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
const (
|
||||
PER_LINUX = C.PER_LINUX
|
||||
PER_LINUX32 = C.PER_LINUX32
|
||||
)
|
||||
|
||||
var syscallNumExtra = map[string]int{
|
||||
"umount": SYS_UMOUNT,
|
||||
"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 (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
O_PATH = 0x200000
|
||||
|
||||
PR_SET_NO_NEW_PRIVS = 0x26
|
||||
|
||||
CAP_SYS_ADMIN = 0x15
|
||||
CAP_SETPCAP = 0x8
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -28,42 +18,6 @@ func SetDumpable(dumpable uintptr) error {
|
||||
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
|
||||
// EINTR error. This appears to be required even though we install all
|
||||
// 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
|
||||
)
|
||||
2
dist/install.sh
vendored
2
dist/install.sh
vendored
@@ -2,7 +2,7 @@
|
||||
cd "$(dirname -- "$0")" || exit 1
|
||||
|
||||
install -vDm0755 "bin/hakurei" "${HAKUREI_INSTALL_PREFIX}/usr/bin/hakurei"
|
||||
install -vDm0755 "bin/planterette" "${HAKUREI_INSTALL_PREFIX}/usr/bin/planterette"
|
||||
install -vDm0755 "bin/hpkg" "${HAKUREI_INSTALL_PREFIX}/usr/bin/hpkg"
|
||||
|
||||
install -vDm6511 "bin/hsu" "${HAKUREI_INSTALL_PREFIX}/usr/bin/hsu"
|
||||
if [ ! -f "${HAKUREI_INSTALL_PREFIX}/etc/hsurc" ]; then
|
||||
|
||||
12
flake.lock
generated
12
flake.lock
generated
@@ -7,11 +7,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1748665073,
|
||||
"narHash": "sha256-RMhjnPKWtCoIIHiuR9QKD7xfsKb3agxzMfJY8V9MOew=",
|
||||
"lastModified": 1753479839,
|
||||
"narHash": "sha256-E/rPVh7vyPMJUFl2NAew+zibNGfVbANr8BP8nLRbLkQ=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "282e1e029cb6ab4811114fc85110613d72771dea",
|
||||
"rev": "0b9bf983db4d064764084cd6748efb1ab8297d1e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -23,11 +23,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1749024892,
|
||||
"narHash": "sha256-OGcDEz60TXQC+gVz5sdtgGJdKVYr6rwdzQKuZAJQpCA=",
|
||||
"lastModified": 1753345091,
|
||||
"narHash": "sha256-CdX2Rtvp5I8HGu9swBmYuq+ILwRxpXdJwlpg8jvN4tU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "8f1b52b04f2cb6e5ead50bd28d76528a2f0380ef",
|
||||
"rev": "3ff0e34b1383648053bba8ed03f201d3466f90c9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
34
flake.nix
34
flake.nix
@@ -32,7 +32,7 @@
|
||||
buildPackage = forAllSystems (
|
||||
system:
|
||||
nixpkgsFor.${system}.callPackage (
|
||||
import ./cmd/planterette/build.nix {
|
||||
import ./cmd/hpkg/build.nix {
|
||||
inherit
|
||||
nixpkgsFor
|
||||
system
|
||||
@@ -69,7 +69,7 @@
|
||||
withRace = true;
|
||||
};
|
||||
|
||||
planterette = callPackage ./cmd/planterette/test { inherit system self; };
|
||||
hpkg = callPackage ./cmd/hpkg/test { inherit system self; };
|
||||
|
||||
formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt-rfc-style ]; } ''
|
||||
cd ${./.}
|
||||
@@ -125,7 +125,7 @@
|
||||
glibc
|
||||
xdg-dbus-proxy
|
||||
|
||||
# planterette
|
||||
# hpkg
|
||||
zstd
|
||||
gnutar
|
||||
coreutils
|
||||
@@ -185,17 +185,23 @@
|
||||
'';
|
||||
};
|
||||
|
||||
generateSyscallTable = pkgs.mkShell {
|
||||
# this should be made cross-platform via nix
|
||||
shellHook = "exec ${pkgs.writeShellScript "generate-syscall-table" ''
|
||||
set -e
|
||||
${pkgs.perl}/bin/perl \
|
||||
sandbox/seccomp/mksysnum_linux.pl \
|
||||
${pkgs.linuxHeaders}/include/asm/unistd_64.h | \
|
||||
${pkgs.go}/bin/gofmt > \
|
||||
sandbox/seccomp/syscall_linux_amd64.go
|
||||
''}";
|
||||
};
|
||||
generateSyscallTable =
|
||||
let
|
||||
GOARCH = {
|
||||
x86_64-linux = "amd64";
|
||||
aarch64-linux = "arm64";
|
||||
};
|
||||
in
|
||||
pkgs.mkShell {
|
||||
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"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/helper"
|
||||
)
|
||||
|
||||
func TestCmd(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) {
|
||||
t.Errorf("Start: error = %v, wantErr %v",
|
||||
|
||||
@@ -4,20 +4,17 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/helper"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
func TestContainer(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 {
|
||||
t.Errorf("Start: error = %v, wantErr %q",
|
||||
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 {
|
||||
return helper.New(ctx, os.Args[0], argsWt, stat, argF, func(z *container.Container) {
|
||||
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")
|
||||
}, 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) {
|
||||
args = make([]string, 0, 6)
|
||||
args = append(args, "-test.run=TestHelperStub", "--")
|
||||
if argsFd > -1 {
|
||||
args = append(args, "--args", strconv.Itoa(argsFd))
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ func InternalHelperStub() {
|
||||
sp = v
|
||||
}
|
||||
|
||||
genericStub(flagRestoreFiles(3, ap, sp))
|
||||
genericStub(flagRestoreFiles(1, ap, sp))
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
package helper_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"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,15 +1,35 @@
|
||||
package hst
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"hakurei.app/container/seccomp"
|
||||
)
|
||||
|
||||
const (
|
||||
// SourceTmpfs causes tmpfs to be mounted on [FilesystemConfig.Dst]
|
||||
// when assigned to [FilesystemConfig.Src].
|
||||
SourceTmpfs = "tmpfs"
|
||||
|
||||
// TmpfsPerm is the permission bits for tmpfs mount points
|
||||
// configured through [FilesystemConfig].
|
||||
TmpfsPerm = 0755
|
||||
|
||||
// TmpfsSize is the size for tmpfs mount points
|
||||
// configured through [FilesystemConfig].
|
||||
TmpfsSize = 0
|
||||
)
|
||||
|
||||
type (
|
||||
// ContainerConfig describes the container configuration baseline to which the app implementation adds upon.
|
||||
ContainerConfig struct {
|
||||
// container hostname
|
||||
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
|
||||
SeccompFlags seccomp.ExportFlag `json:"seccomp_flags"`
|
||||
// extra seccomp presets
|
||||
@@ -39,10 +59,17 @@ type (
|
||||
// create symlinks inside container filesystem
|
||||
Link [][2]string `json:"symlink"`
|
||||
|
||||
// automatically bind mount top-level directories to container root;
|
||||
// the zero value disables this behaviour
|
||||
AutoRoot string `json:"auto_root,omitempty"`
|
||||
// extra flags for AutoRoot
|
||||
RootFlags int `json:"root_flags,omitempty"`
|
||||
|
||||
// read-only /etc directory
|
||||
Etc string `json:"etc,omitempty"`
|
||||
// automatically set up /etc symlinks
|
||||
AutoEtc bool `json:"auto_etc"`
|
||||
|
||||
// cover these paths or create them if they do not already exist
|
||||
Cover []string `json:"cover"`
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package hst
|
||||
|
||||
import (
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/dbus"
|
||||
@@ -62,8 +63,10 @@ func Template() *Config {
|
||||
Userns: true,
|
||||
Net: true,
|
||||
Device: true,
|
||||
WaitDelay: -1,
|
||||
SeccompFlags: seccomp.AllowMultiarch,
|
||||
SeccompPresets: seccomp.PresetExt,
|
||||
SeccompCompat: true,
|
||||
Tty: true,
|
||||
Multiarch: true,
|
||||
MapRealUID: true,
|
||||
@@ -83,10 +86,12 @@ func Template() *Config {
|
||||
Dst: "/data/data/org.chromium.Chromium", Write: true, Must: true},
|
||||
{Src: "/dev/dri", Device: true},
|
||||
},
|
||||
Link: [][2]string{{"/run/user/65534", "/run/user/150"}},
|
||||
Etc: "/etc",
|
||||
AutoEtc: true,
|
||||
Cover: []string{"/var/run/nscd"},
|
||||
Link: [][2]string{{"/run/user/65534", "/run/user/150"}},
|
||||
AutoRoot: "/var/lib/hakurei/base/org.debian",
|
||||
RootFlags: container.BindWritable,
|
||||
Etc: "/etc",
|
||||
AutoEtc: true,
|
||||
Cover: []string{"/var/run/nscd"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,8 +80,10 @@ func TestTemplate(t *testing.T) {
|
||||
],
|
||||
"container": {
|
||||
"hostname": "localhost",
|
||||
"wait_delay": -1,
|
||||
"seccomp_flags": 1,
|
||||
"seccomp_presets": 1,
|
||||
"seccomp_compat": true,
|
||||
"devel": true,
|
||||
"userns": true,
|
||||
"net": true,
|
||||
@@ -124,6 +126,8 @@ func TestTemplate(t *testing.T) {
|
||||
"/run/user/150"
|
||||
]
|
||||
],
|
||||
"auto_root": "/var/lib/hakurei/base/org.debian",
|
||||
"root_flags": 2,
|
||||
"etc": "/etc",
|
||||
"auto_etc": true,
|
||||
"cover": [
|
||||
|
||||
@@ -2,15 +2,19 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/sys"
|
||||
)
|
||||
|
||||
type App interface {
|
||||
// ID returns a copy of [ID] held by App.
|
||||
ID() ID
|
||||
ID() state.ID
|
||||
|
||||
// Seal determines the outcome of config as a [SealedApp].
|
||||
// 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()
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
. "hakurei.app/cmd/hakurei/internal/app"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/hlog"
|
||||
"hakurei.app/internal/sys"
|
||||
)
|
||||
@@ -16,15 +16,15 @@ func New(ctx context.Context, os sys.State) (App, error) {
|
||||
a.sys = os
|
||||
a.ctx = ctx
|
||||
|
||||
id := new(ID)
|
||||
err := NewAppID(id)
|
||||
id := new(state.ID)
|
||||
err := state.NewAppID(id)
|
||||
a.id = newID(id)
|
||||
|
||||
return a, err
|
||||
}
|
||||
|
||||
type app struct {
|
||||
id *stringPair[ID]
|
||||
id *stringPair[state.ID]
|
||||
sys sys.State
|
||||
ctx context.Context
|
||||
|
||||
@@ -32,7 +32,7 @@ type app struct {
|
||||
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 {
|
||||
if a == nil {
|
||||
@@ -1,4 +1,4 @@
|
||||
package setuid_test
|
||||
package app_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hakurei.app/cmd/hakurei/internal/app"
|
||||
"hakurei.app/cmd/hakurei/internal/app/internal/setuid"
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/sys"
|
||||
"hakurei.app/system"
|
||||
)
|
||||
@@ -19,7 +19,7 @@ type sealTestCase struct {
|
||||
name string
|
||||
os sys.State
|
||||
config *hst.Config
|
||||
id app.ID
|
||||
id state.ID
|
||||
wantSys *system.I
|
||||
wantContainer *container.Params
|
||||
}
|
||||
@@ -29,7 +29,7 @@ func TestApp(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
a := setuid.NewWithID(tc.id, tc.os)
|
||||
a := app.NewWithID(tc.id, tc.os)
|
||||
var (
|
||||
gotSys *system.I
|
||||
gotContainer *container.Params
|
||||
@@ -39,7 +39,7 @@ func TestApp(t *testing.T) {
|
||||
t.Errorf("Seal: error = %v", err)
|
||||
return
|
||||
} else {
|
||||
gotSys, gotContainer = setuid.AppIParams(a, sa)
|
||||
gotSys, gotContainer = app.AppIParams(a, sa)
|
||||
}
|
||||
}) {
|
||||
return
|
||||
@@ -1,10 +1,12 @@
|
||||
package setuid_test
|
||||
package app_test
|
||||
|
||||
import (
|
||||
"hakurei.app/cmd/hakurei/internal/app"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/acl"
|
||||
"hakurei.app/system/dbus"
|
||||
@@ -52,7 +54,7 @@ var testCasesNixos = []sealTestCase{
|
||||
Data: "/var/lib/persist/module/hakurei/0/1",
|
||||
Identity: 1, Groups: []string{},
|
||||
},
|
||||
app.ID{
|
||||
state.ID{
|
||||
0x8e, 0x2c, 0x76, 0xb0,
|
||||
0x66, 0xda, 0xbe, 0x57,
|
||||
0x4c, 0xf0, 0x73, 0xbd,
|
||||
@@ -141,9 +143,11 @@ var testCasesNixos = []sealTestCase{
|
||||
Place(hst.Tmp+"/pulse-cookie", nil).
|
||||
Bind("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", "/run/user/1971/bus", 0).
|
||||
Bind("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", "/run/dbus/system_bus_socket", 0).
|
||||
Tmpfs("/var/run/nscd", 8192, 0755),
|
||||
Tmpfs("/var/run/nscd", 8192, 0755).
|
||||
Remount("/", syscall.MS_RDONLY),
|
||||
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyTTY | seccomp.PresetDenyDevel,
|
||||
HostNet: true,
|
||||
ForwardCancel: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package setuid_test
|
||||
package app_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/cmd/hakurei/internal/app"
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/system"
|
||||
"hakurei.app/system/acl"
|
||||
"hakurei.app/system/dbus"
|
||||
@@ -16,7 +17,7 @@ var testCasesPd = []sealTestCase{
|
||||
{
|
||||
"nixos permissive defaults no enablements", new(stubNixOS),
|
||||
&hst.Config{Username: "chronos", Data: "/home/chronos"},
|
||||
app.ID{
|
||||
state.ID{
|
||||
0x4a, 0x45, 0x0b, 0x65,
|
||||
0x96, 0xd7, 0xbc, 0x15,
|
||||
0xbd, 0x01, 0x78, 0x0e,
|
||||
@@ -42,22 +43,12 @@ var testCasesPd = []sealTestCase{
|
||||
"XDG_SESSION_TYPE=tty",
|
||||
},
|
||||
Ops: new(container.Ops).
|
||||
Root("/", "4a450b6596d7bc15bd01780eb9a607ac", container.BindWritable).
|
||||
Proc("/proc").
|
||||
Tmpfs(hst.Tmp, 4096, 0755).
|
||||
Dev("/dev").Mqueue("/dev/mqueue").
|
||||
Bind("/bin", "/bin", container.BindWritable).
|
||||
Bind("/boot", "/boot", container.BindWritable).
|
||||
Bind("/home", "/home", container.BindWritable).
|
||||
Bind("/lib", "/lib", container.BindWritable).
|
||||
Bind("/lib64", "/lib64", container.BindWritable).
|
||||
Bind("/nix", "/nix", container.BindWritable).
|
||||
Bind("/root", "/root", container.BindWritable).
|
||||
Bind("/run", "/run", container.BindWritable).
|
||||
Bind("/srv", "/srv", container.BindWritable).
|
||||
Bind("/sys", "/sys", container.BindWritable).
|
||||
Bind("/usr", "/usr", container.BindWritable).
|
||||
Bind("/var", "/var", container.BindWritable).
|
||||
Bind("/dev/kvm", "/dev/kvm", container.BindWritable|container.BindDevice|container.BindOptional).
|
||||
Readonly("/var/run/nscd", 0755).
|
||||
Tmpfs("/run/user/1971", 8192, 0755).
|
||||
Tmpfs("/run/dbus", 8192, 0755).
|
||||
Etc("/etc", "4a450b6596d7bc15bd01780eb9a607ac").
|
||||
@@ -67,10 +58,11 @@ var testCasesPd = []sealTestCase{
|
||||
Bind("/home/chronos", "/home/chronos", container.BindWritable).
|
||||
Place("/etc/passwd", []byte("chronos:x:65534:65534:Hakurei:/home/chronos:/run/current-system/sw/bin/zsh\n")).
|
||||
Place("/etc/group", []byte("hakurei:x:65534:\n")).
|
||||
Tmpfs("/var/run/nscd", 8192, 0755),
|
||||
Remount("/", syscall.MS_RDONLY),
|
||||
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel,
|
||||
HostNet: true,
|
||||
RetainSession: true,
|
||||
ForwardCancel: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -115,7 +107,7 @@ var testCasesPd = []sealTestCase{
|
||||
},
|
||||
Enablements: system.EWayland | system.EDBus | system.EPulse,
|
||||
},
|
||||
app.ID{
|
||||
state.ID{
|
||||
0xeb, 0xf0, 0x83, 0xd1,
|
||||
0xb1, 0x75, 0x91, 0x17,
|
||||
0x82, 0xd4, 0x13, 0x36,
|
||||
@@ -185,23 +177,13 @@ var testCasesPd = []sealTestCase{
|
||||
"XDG_SESSION_TYPE=tty",
|
||||
},
|
||||
Ops: new(container.Ops).
|
||||
Root("/", "ebf083d1b175911782d413369b64ce7c", container.BindWritable).
|
||||
Proc("/proc").
|
||||
Tmpfs(hst.Tmp, 4096, 0755).
|
||||
Dev("/dev").Mqueue("/dev/mqueue").
|
||||
Bind("/bin", "/bin", container.BindWritable).
|
||||
Bind("/boot", "/boot", container.BindWritable).
|
||||
Bind("/home", "/home", container.BindWritable).
|
||||
Bind("/lib", "/lib", container.BindWritable).
|
||||
Bind("/lib64", "/lib64", container.BindWritable).
|
||||
Bind("/nix", "/nix", container.BindWritable).
|
||||
Bind("/root", "/root", container.BindWritable).
|
||||
Bind("/run", "/run", container.BindWritable).
|
||||
Bind("/srv", "/srv", container.BindWritable).
|
||||
Bind("/sys", "/sys", container.BindWritable).
|
||||
Bind("/usr", "/usr", container.BindWritable).
|
||||
Bind("/var", "/var", container.BindWritable).
|
||||
Bind("/dev/dri", "/dev/dri", container.BindWritable|container.BindDevice|container.BindOptional).
|
||||
Bind("/dev/kvm", "/dev/kvm", container.BindWritable|container.BindDevice|container.BindOptional).
|
||||
Readonly("/var/run/nscd", 0755).
|
||||
Tmpfs("/run/user/1971", 8192, 0755).
|
||||
Tmpfs("/run/dbus", 8192, 0755).
|
||||
Etc("/etc", "ebf083d1b175911782d413369b64ce7c").
|
||||
@@ -216,10 +198,11 @@ var testCasesPd = []sealTestCase{
|
||||
Place(hst.Tmp+"/pulse-cookie", nil).
|
||||
Bind("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/bus", "/run/user/65534/bus", 0).
|
||||
Bind("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", "/run/dbus/system_bus_socket", 0).
|
||||
Tmpfs("/var/run/nscd", 8192, 0755),
|
||||
Remount("/", syscall.MS_RDONLY),
|
||||
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel,
|
||||
HostNet: true,
|
||||
RetainSession: true,
|
||||
ForwardCancel: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package setuid_test
|
||||
package app_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"io/fs"
|
||||
"maps"
|
||||
"path"
|
||||
"slices"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/container"
|
||||
@@ -19,9 +20,9 @@ import (
|
||||
// allocating slightly more as a margin for future expansion
|
||||
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.
|
||||
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, prefix string, uid, gid *int) (*container.Params, map[string]string, error) {
|
||||
if s == nil {
|
||||
return nil, nil, syscall.EBADE
|
||||
}
|
||||
@@ -32,6 +33,10 @@ func NewContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain
|
||||
SeccompPresets: s.SeccompPresets,
|
||||
RetainSession: s.Tty,
|
||||
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,
|
||||
}
|
||||
|
||||
{
|
||||
@@ -68,6 +73,13 @@ func NewContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain
|
||||
*gid = container.OverflowGid()
|
||||
}
|
||||
|
||||
if s.AutoRoot != "" {
|
||||
if !path.IsAbs(s.AutoRoot) {
|
||||
return nil, nil, fmt.Errorf("auto root target %q not absolute", s.AutoRoot)
|
||||
}
|
||||
params.Root(s.AutoRoot, prefix, s.RootFlags)
|
||||
}
|
||||
|
||||
params.
|
||||
Proc("/proc").
|
||||
Tmpfs(hst.Tmp, 1<<12, 0755)
|
||||
@@ -116,12 +128,48 @@ func NewContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
// evaluated path, input path
|
||||
hidePathSource := make([][2]string, 0, len(s.Filesystem))
|
||||
|
||||
// AutoRoot is a collection of many BindMountOp internally
|
||||
if s.AutoRoot != "" {
|
||||
if d, err := os.ReadDir(s.AutoRoot); err != nil {
|
||||
return nil, nil, err
|
||||
} else {
|
||||
hidePathSource = slices.Grow(hidePathSource, len(d))
|
||||
for _, ent := range d {
|
||||
name := ent.Name()
|
||||
if container.IsAutoRootBindable(name) {
|
||||
name = path.Join(s.AutoRoot, name)
|
||||
srcP := [2]string{name, name}
|
||||
if err = evalSymlinks(os, &srcP[0]); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
hidePathSource = append(hidePathSource, srcP)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range s.Filesystem {
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// special filesystems
|
||||
switch c.Src {
|
||||
case hst.SourceTmpfs:
|
||||
if !path.IsAbs(c.Dst) {
|
||||
return nil, nil, fmt.Errorf("tmpfs dst %q is not absolute", c.Dst)
|
||||
}
|
||||
if c.Write {
|
||||
params.Tmpfs(c.Dst, hst.TmpfsSize, hst.TmpfsPerm)
|
||||
} else {
|
||||
params.Readonly(c.Dst, hst.TmpfsPerm)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !path.IsAbs(c.Src) {
|
||||
return nil, nil, fmt.Errorf("src path %q is not absolute", c.Src)
|
||||
}
|
||||
@@ -133,24 +181,11 @@ func NewContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain
|
||||
return nil, nil, fmt.Errorf("dst path %q is not absolute", dest)
|
||||
}
|
||||
|
||||
srcH := c.Src
|
||||
if err := evalSymlinks(os, &srcH); err != nil {
|
||||
p := [2]string{c.Src, c.Src}
|
||||
if err := evalSymlinks(os, &p[0]); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for i := range hidePaths {
|
||||
// skip matched entries
|
||||
if hidePathMatch[i] {
|
||||
continue
|
||||
}
|
||||
|
||||
if ok, err := deepContainsH(srcH, hidePaths[i]); err != nil {
|
||||
return nil, nil, err
|
||||
} else if ok {
|
||||
hidePathMatch[i] = true
|
||||
os.Printf("hiding paths from %q", c.Src)
|
||||
}
|
||||
}
|
||||
hidePathSource = append(hidePathSource, p)
|
||||
|
||||
var flags int
|
||||
if c.Write {
|
||||
@@ -165,6 +200,22 @@ func NewContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain
|
||||
params.Bind(c.Src, dest, flags)
|
||||
}
|
||||
|
||||
for _, p := range hidePathSource {
|
||||
for i := range hidePaths {
|
||||
// skip matched entries
|
||||
if hidePathMatch[i] {
|
||||
continue
|
||||
}
|
||||
|
||||
if ok, err := deepContainsH(p[0], hidePaths[i]); err != nil {
|
||||
return nil, nil, err
|
||||
} else if ok {
|
||||
hidePathMatch[i] = true
|
||||
os.Printf("hiding path %q from %q", hidePaths[i], p[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cover matched paths
|
||||
for i, ok := range hidePathMatch {
|
||||
if ok {
|
||||
@@ -176,6 +227,18 @@ func NewContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain
|
||||
params.Link(l[0], l[1])
|
||||
}
|
||||
|
||||
if !s.AutoEtc {
|
||||
if s.Etc != "" {
|
||||
params.Bind(s.Etc, "/etc", 0)
|
||||
}
|
||||
} else {
|
||||
etcPath := s.Etc
|
||||
if etcPath == "" {
|
||||
etcPath = "/etc"
|
||||
}
|
||||
params.Etc(etcPath, prefix)
|
||||
}
|
||||
|
||||
return params, maps.Clone(s.Env), nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package setuid
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
. "hakurei.app/cmd/hakurei/internal/app"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package setuid
|
||||
package app
|
||||
|
||||
import (
|
||||
. "hakurei.app/cmd/hakurei/internal/app"
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/sys"
|
||||
"hakurei.app/system"
|
||||
)
|
||||
|
||||
func NewWithID(id ID, os sys.State) App {
|
||||
func NewWithID(id state.ID, os sys.State) App {
|
||||
a := new(app)
|
||||
a.id = newID(&id)
|
||||
a.sys = os
|
||||
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package app
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package app
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,4 +1,4 @@
|
||||
package setuid
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -12,10 +12,9 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
. "hakurei.app/cmd/hakurei/internal/app"
|
||||
"hakurei.app/cmd/hakurei/internal/state"
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/hlog"
|
||||
"hakurei.app/system"
|
||||
)
|
||||
@@ -124,7 +123,15 @@ func (seal *outcome) Run(rs *RunState) error {
|
||||
// this prevents blocking forever on an early failure
|
||||
waitErr, setupErr := make(chan error, 1), make(chan error, 1)
|
||||
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 {
|
||||
case err := <-setupErr:
|
||||
@@ -1,4 +1,4 @@
|
||||
package setuid
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -15,12 +15,12 @@ import (
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
. "hakurei.app/cmd/hakurei/internal/app"
|
||||
"hakurei.app/cmd/hakurei/internal/app/instance/common"
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/hlog"
|
||||
"hakurei.app/internal/sys"
|
||||
"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]
|
||||
type outcome struct {
|
||||
// copied from initialising [app]
|
||||
id *stringPair[ID]
|
||||
id *stringPair[state.ID]
|
||||
// copied from [sys.State] response
|
||||
runDirPath string
|
||||
|
||||
@@ -80,6 +80,7 @@ type outcome struct {
|
||||
sys *system.I
|
||||
ctx context.Context
|
||||
|
||||
waitDelay time.Duration
|
||||
container *container.Params
|
||||
env map[string]string
|
||||
sync *os.File
|
||||
@@ -240,33 +241,11 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
||||
Net: true,
|
||||
Tty: true,
|
||||
AutoEtc: true,
|
||||
}
|
||||
// bind entries in /
|
||||
if d, err := sys.ReadDir("/"); err != nil {
|
||||
return err
|
||||
} else {
|
||||
b := make([]*hst.FilesystemConfig, 0, len(d))
|
||||
for _, ent := range d {
|
||||
p := "/" + ent.Name()
|
||||
switch p {
|
||||
case "/proc":
|
||||
case "/dev":
|
||||
case "/tmp":
|
||||
case "/mnt":
|
||||
case "/etc":
|
||||
|
||||
default:
|
||||
b = append(b, &hst.FilesystemConfig{Src: p, Write: true, Must: true})
|
||||
}
|
||||
}
|
||||
conf.Filesystem = append(conf.Filesystem, b...)
|
||||
AutoRoot: "/",
|
||||
RootFlags: container.BindWritable,
|
||||
}
|
||||
|
||||
// hide nscd from sandbox if present
|
||||
nscd := "/var/run/nscd"
|
||||
if _, err := sys.Stat(nscd); !errors.Is(err, fs.ErrNotExist) {
|
||||
conf.Cover = append(conf.Cover, nscd)
|
||||
}
|
||||
// bind GPU stuff
|
||||
if config.Enablements&(system.EX11|system.EWayland) != 0 {
|
||||
conf.Filesystem = append(conf.Filesystem, &hst.FilesystemConfig{Src: "/dev/dri", Device: true})
|
||||
@@ -274,6 +253,12 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
||||
// opportunistically bind kvm
|
||||
conf.Filesystem = append(conf.Filesystem, &hst.FilesystemConfig{Src: "/dev/kvm", Device: true})
|
||||
|
||||
// hide nscd from container if present
|
||||
const nscd = "/var/run/nscd"
|
||||
if _, err := sys.Stat(nscd); !errors.Is(err, fs.ErrNotExist) {
|
||||
conf.Filesystem = append(conf.Filesystem, &hst.FilesystemConfig{Dst: nscd, Src: hst.SourceTmpfs})
|
||||
}
|
||||
|
||||
config.Container = conf
|
||||
}
|
||||
|
||||
@@ -281,7 +266,8 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
||||
{
|
||||
var uid, gid int
|
||||
var err error
|
||||
seal.container, seal.env, err = common.NewContainer(config.Container, sys, &uid, &gid)
|
||||
seal.container, seal.env, err = newContainer(config.Container, sys, seal.id.String(), &uid, &gid)
|
||||
seal.waitDelay = config.Container.WaitDelay
|
||||
if err != nil {
|
||||
return hlog.WrapErrSuffix(err,
|
||||
"cannot initialise container configuration:")
|
||||
@@ -303,18 +289,6 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
||||
}
|
||||
}
|
||||
|
||||
if !config.Container.AutoEtc {
|
||||
if config.Container.Etc != "" {
|
||||
seal.container.Bind(config.Container.Etc, "/etc", 0)
|
||||
}
|
||||
} else {
|
||||
etcPath := config.Container.Etc
|
||||
if etcPath == "" {
|
||||
etcPath = "/etc"
|
||||
}
|
||||
seal.container.Etc(etcPath, seal.id.String())
|
||||
}
|
||||
|
||||
// inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` as mapped uid
|
||||
innerRuntimeDir := path.Join("/run/user", mapuid.String())
|
||||
seal.env[xdgRuntimeDir] = innerRuntimeDir
|
||||
@@ -504,6 +478,9 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
||||
seal.container.Tmpfs(dest, 1<<13, 0755)
|
||||
}
|
||||
|
||||
// mount root read-only as the final setup Op
|
||||
seal.container.Remount("/", syscall.MS_RDONLY)
|
||||
|
||||
// append ExtraPerms last
|
||||
for _, p := range config.ExtraPerms {
|
||||
if p == nil {
|
||||
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 (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -16,55 +19,7 @@ import (
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
/*
|
||||
#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;
|
||||
}
|
||||
*/
|
||||
//#include "shim-signal.h"
|
||||
import "C"
|
||||
|
||||
const shimEnv = "HAKUREI_SHIM"
|
||||
@@ -73,6 +28,10 @@ type shimParams struct {
|
||||
// monitor pid, checked against ppid in signal handler
|
||||
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
|
||||
Container *container.Params
|
||||
// path to outer home directory
|
||||
@@ -82,6 +41,16 @@ type shimParams struct {
|
||||
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.
|
||||
func ShimMain() {
|
||||
hlog.Prepare("shim")
|
||||
@@ -106,18 +75,63 @@ func ShimMain() {
|
||||
} else {
|
||||
internal.InstallOutput(params.Verbose)
|
||||
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 {
|
||||
log.Fatal("invalid container params")
|
||||
}
|
||||
@@ -148,12 +162,18 @@ func ShimMain() {
|
||||
name = params.Container.Args[0]
|
||||
}
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop() // unreachable
|
||||
cancelContainer.Store(&stop)
|
||||
z := container.New(ctx, name)
|
||||
z.Params = *params.Container
|
||||
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 {
|
||||
hlog.PrintBaseError(err, "cannot start container:")
|
||||
@@ -1,4 +1,4 @@
|
||||
package app
|
||||
package state
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
@@ -1,22 +1,22 @@
|
||||
package app_test
|
||||
package state_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
. "hakurei.app/cmd/hakurei/internal/app"
|
||||
"hakurei.app/internal/app/state"
|
||||
)
|
||||
|
||||
func TestParseAppID(t *testing.T) {
|
||||
t.Run("bad length", func(t *testing.T) {
|
||||
if err := ParseAppID(new(ID), "meow"); !errors.Is(err, ErrInvalidLength) {
|
||||
t.Errorf("ParseAppID: error = %v, wantErr = %v", err, ErrInvalidLength)
|
||||
if err := state.ParseAppID(new(state.ID), "meow"); !errors.Is(err, state.ErrInvalidLength) {
|
||||
t.Errorf("ParseAppID: error = %v, wantErr = %v", err, state.ErrInvalidLength)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("bad byte", func(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
})
|
||||
@@ -30,30 +30,30 @@ func TestParseAppID(t *testing.T) {
|
||||
|
||||
func FuzzParseAppID(f *testing.F) {
|
||||
for i := 0; i < 16; i++ {
|
||||
id := new(ID)
|
||||
if err := NewAppID(id); err != nil {
|
||||
id := new(state.ID)
|
||||
if err := state.NewAppID(id); err != nil {
|
||||
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.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) {
|
||||
id := new(ID)
|
||||
if err := NewAppID(id); err != nil {
|
||||
id := new(state.ID)
|
||||
if err := state.NewAppID(id); err != nil {
|
||||
t.Fatalf("cannot generate app ID: %v", err)
|
||||
}
|
||||
testParseAppID(t, id)
|
||||
}
|
||||
|
||||
func testParseAppID(t *testing.T, id *ID) {
|
||||
func testParseAppID(t *testing.T, id *state.ID) {
|
||||
s := id.String()
|
||||
got := new(ID)
|
||||
if err := ParseAppID(got, s); err != nil {
|
||||
got := new(state.ID)
|
||||
if err := state.ParseAppID(got, s); err != nil {
|
||||
t.Fatalf("cannot parse app ID: %v", err)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/cmd/hakurei/internal/app"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
@@ -130,7 +129,7 @@ type multiBackend struct {
|
||||
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())
|
||||
}
|
||||
|
||||
@@ -190,8 +189,8 @@ func (b *multiBackend) load(decode bool) (Entries, error) {
|
||||
return nil, fmt.Errorf("unexpected directory %q in store", e.Name())
|
||||
}
|
||||
|
||||
id := new(app.ID)
|
||||
if err := app.ParseAppID(id, e.Name()); err != nil {
|
||||
id := new(ID)
|
||||
if err := ParseAppID(id, e.Name()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -336,7 +335,7 @@ func (b *multiBackend) encodeState(w io.WriteSeeker, state *State, configWriter
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *multiBackend) Destroy(id app.ID) error {
|
||||
func (b *multiBackend) Destroy(id ID) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
@@ -3,7 +3,7 @@ package state_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"hakurei.app/cmd/hakurei/internal/state"
|
||||
"hakurei.app/internal/app/state"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
@@ -5,13 +6,12 @@ import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"hakurei.app/cmd/hakurei/internal/app"
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
var ErrNoConfig = errors.New("state does not contain config")
|
||||
|
||||
type Entries map[app.ID]*State
|
||||
type Entries map[ID]*State
|
||||
|
||||
type Store interface {
|
||||
// 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
|
||||
type Cursor interface {
|
||||
Save(state *State, configWriter io.WriterTo) error
|
||||
Destroy(id app.ID) error
|
||||
Destroy(id ID) error
|
||||
Load() (Entries, error)
|
||||
Len() (int, error)
|
||||
}
|
||||
@@ -38,7 +38,7 @@ type Cursor interface {
|
||||
// State is an instance state
|
||||
type State struct {
|
||||
// hakurei instance id
|
||||
ID app.ID `json:"instance"`
|
||||
ID ID `json:"instance"`
|
||||
// child process PID value
|
||||
PID int `json:"pid"`
|
||||
// sealed app configuration
|
||||
@@ -10,9 +10,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hakurei.app/cmd/hakurei/internal/app"
|
||||
"hakurei.app/cmd/hakurei/internal/state"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app/state"
|
||||
)
|
||||
|
||||
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) {
|
||||
if err := app.NewAppID(&s.ID); err != nil {
|
||||
if err := state.NewAppID(&s.ID); err != nil {
|
||||
t.Fatalf("cannot create dummy state: %v", err)
|
||||
}
|
||||
if err := gob.NewEncoder(ct).Encode(hst.Template()); err != nil {
|
||||
@@ -1,13 +1,13 @@
|
||||
package setuid
|
||||
package app
|
||||
|
||||
import (
|
||||
"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 newID(id *ID) *stringPair[ID] { return &stringPair[ID]{*id, id.String()} }
|
||||
func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} }
|
||||
func newID(id *state.ID) *stringPair[state.ID] { return &stringPair[state.ID]{*id, id.String()} }
|
||||
|
||||
// stringPair stores a value and its string representation.
|
||||
type stringPair[T comparable] struct {
|
||||
12
ldd/exec.go
12
ldd/exec.go
@@ -5,7 +5,6 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"hakurei.app/container"
|
||||
@@ -19,16 +18,10 @@ var (
|
||||
msgStaticGlibc = []byte("not a dynamic executable")
|
||||
)
|
||||
|
||||
func Exec(ctx context.Context, p string) ([]*Entry, error) { return ExecFilter(ctx, nil, nil, p) }
|
||||
|
||||
func ExecFilter(ctx context.Context,
|
||||
commandContext func(context.Context) *exec.Cmd,
|
||||
f func([]byte) []byte,
|
||||
p string) ([]*Entry, error) {
|
||||
func Exec(ctx context.Context, p string) ([]*Entry, error) {
|
||||
c, cancel := context.WithTimeout(ctx, lddTimeout)
|
||||
defer cancel()
|
||||
z := container.New(c, "ldd", p)
|
||||
z.CommandContext = commandContext
|
||||
z.Hostname = "hakurei-ldd"
|
||||
z.SeccompFlags |= seccomp.AllowMultiarch
|
||||
z.SeccompPresets |= seccomp.PresetStrict
|
||||
@@ -54,8 +47,5 @@ func ExecFilter(ctx context.Context,
|
||||
}
|
||||
|
||||
v := stdout.Bytes()
|
||||
if f != nil {
|
||||
v = f(v)
|
||||
}
|
||||
return Parse(v)
|
||||
}
|
||||
|
||||
34
nixos.nix
34
nixos.nix
@@ -82,7 +82,8 @@ in
|
||||
own = [
|
||||
"${id}.*"
|
||||
"org.mpris.MediaPlayer2.${id}.*"
|
||||
] ++ ext.own;
|
||||
]
|
||||
++ ext.own;
|
||||
|
||||
inherit (ext) call broadcast;
|
||||
};
|
||||
@@ -127,6 +128,7 @@ in
|
||||
|
||||
container = {
|
||||
inherit (app)
|
||||
wait_delay
|
||||
devel
|
||||
userns
|
||||
net
|
||||
@@ -173,29 +175,27 @@ in
|
||||
++ optionals app.useCommonPaths cfg.commonPaths
|
||||
++ app.extraPaths;
|
||||
auto_etc = true;
|
||||
cover = [ "/var/run/nscd" ];
|
||||
|
||||
symlink =
|
||||
symlink = [
|
||||
[
|
||||
"*/run/current-system"
|
||||
"/run/current-system"
|
||||
]
|
||||
]
|
||||
++ optionals (isGraphical && config.hardware.graphics.enable) (
|
||||
[
|
||||
[
|
||||
"*/run/current-system"
|
||||
"/run/current-system"
|
||||
config.systemd.tmpfiles.settings.graphics-driver."/run/opengl-driver"."L+".argument
|
||||
"/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"."L+".argument
|
||||
"/run/opengl-driver"
|
||||
]
|
||||
config.systemd.tmpfiles.settings.graphics-driver."/run/opengl-driver-32"."L+".argument
|
||||
/run/opengl-driver-32
|
||||
]
|
||||
++ 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 =
|
||||
let
|
||||
inherit (types)
|
||||
int
|
||||
ints
|
||||
str
|
||||
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";
|
||||
userns = mkEnableOption "user namespace creation";
|
||||
tty = mkEnableOption "access to the controlling terminal";
|
||||
|
||||
52
package.nix
52
package.nix
@@ -13,7 +13,7 @@
|
||||
wayland-scanner,
|
||||
xorg,
|
||||
|
||||
# for planterette
|
||||
# for hpkg
|
||||
zstd,
|
||||
gnutar,
|
||||
coreutils,
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
buildGoModule rec {
|
||||
pname = "hakurei";
|
||||
version = "0.1.0";
|
||||
version = "0.1.3";
|
||||
|
||||
srcFiltered = builtins.path {
|
||||
name = "${pname}-src";
|
||||
@@ -83,18 +83,17 @@ buildGoModule rec {
|
||||
# nix build environment does not allow acls
|
||||
env.GO_TEST_SKIP_ACL = 1;
|
||||
|
||||
buildInputs =
|
||||
[
|
||||
libffi
|
||||
libseccomp
|
||||
acl
|
||||
wayland
|
||||
]
|
||||
++ (with xorg; [
|
||||
libxcb
|
||||
libXau
|
||||
libXdmcp
|
||||
]);
|
||||
buildInputs = [
|
||||
libffi
|
||||
libseccomp
|
||||
acl
|
||||
wayland
|
||||
]
|
||||
++ (with xorg; [
|
||||
libxcb
|
||||
libXau
|
||||
libXdmcp
|
||||
]);
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkg-config
|
||||
@@ -117,7 +116,7 @@ buildGoModule rec {
|
||||
makeBinaryWrapper "$out/libexec/hakurei" "$out/bin/hakurei" \
|
||||
--inherit-argv0 --prefix PATH : ${lib.makeBinPath appPackages}
|
||||
|
||||
makeBinaryWrapper "$out/libexec/planterette" "$out/bin/planterette" \
|
||||
makeBinaryWrapper "$out/libexec/hpkg" "$out/bin/hpkg" \
|
||||
--inherit-argv0 --prefix PATH : ${
|
||||
lib.makeBinPath (
|
||||
appPackages
|
||||
@@ -130,17 +129,16 @@ buildGoModule rec {
|
||||
}
|
||||
'';
|
||||
|
||||
passthru.targetPkgs =
|
||||
[
|
||||
go
|
||||
gcc
|
||||
xorg.xorgproto
|
||||
util-linux
|
||||
passthru.targetPkgs = [
|
||||
go
|
||||
gcc
|
||||
xorg.xorgproto
|
||||
util-linux
|
||||
|
||||
# for go generate
|
||||
wayland-protocols
|
||||
wayland-scanner
|
||||
]
|
||||
++ buildInputs
|
||||
++ nativeBuildInputs;
|
||||
# for go generate
|
||||
wayland-protocols
|
||||
wayland-scanner
|
||||
]
|
||||
++ buildInputs
|
||||
++ nativeBuildInputs;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package system
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/system/acl"
|
||||
)
|
||||
|
||||
@@ -52,19 +53,19 @@ func TestACLString(t *testing.T) {
|
||||
et Enablement
|
||||
perms []acl.Perm
|
||||
}{
|
||||
{`--- type: process path: "/nonexistent"`, Process, []acl.Perm{}},
|
||||
{`r-- type: user path: "/nonexistent"`, User, []acl.Perm{acl.Read}},
|
||||
{`-w- type: wayland path: "/nonexistent"`, EWayland, []acl.Perm{acl.Write}},
|
||||
{`--x type: x11 path: "/nonexistent"`, EX11, []acl.Perm{acl.Execute}},
|
||||
{`rw- type: dbus path: "/nonexistent"`, EDBus, []acl.Perm{acl.Read, acl.Write}},
|
||||
{`r-x type: pulseaudio path: "/nonexistent"`, EPulse, []acl.Perm{acl.Read, acl.Execute}},
|
||||
{`rwx type: user path: "/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}},
|
||||
{`--- type: process path: "/proc/nonexistent"`, Process, []acl.Perm{}},
|
||||
{`r-- type: user path: "/proc/nonexistent"`, User, []acl.Perm{acl.Read}},
|
||||
{`-w- type: wayland path: "/proc/nonexistent"`, EWayland, []acl.Perm{acl.Write}},
|
||||
{`--x type: x11 path: "/proc/nonexistent"`, EX11, []acl.Perm{acl.Execute}},
|
||||
{`rw- type: dbus path: "/proc/nonexistent"`, EDBus, []acl.Perm{acl.Read, acl.Write}},
|
||||
{`r-x type: pulseaudio path: "/proc/nonexistent"`, EPulse, []acl.Perm{acl.Read, acl.Execute}},
|
||||
{`rwx type: user path: "/proc/nonexistent"`, User, []acl.Perm{acl.Read, 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 {
|
||||
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 {
|
||||
t.Errorf("String() = %v, want %v",
|
||||
got, tc.want)
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
package dbus_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/helper"
|
||||
"hakurei.app/internal"
|
||||
"hakurei.app/internal/hlog"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
@@ -64,20 +59,23 @@ func TestFinalise(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestProxyStartWaitCloseString(t *testing.T) {
|
||||
oldWaitDelay := helper.WaitDelay
|
||||
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("sandbox", func(t *testing.T) { testProxyFinaliseStartWaitCloseString(t, true) })
|
||||
t.Run("direct", func(t *testing.T) { testProxyFinaliseStartWaitCloseString(t, false) })
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
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)
|
||||
if !useSandbox {
|
||||
p = dbus.NewDirect(ctx, final, output)
|
||||
} else {
|
||||
p = dbus.New(ctx, final, output)
|
||||
}
|
||||
|
||||
t.Run("invalid wait", func(t *testing.T) {
|
||||
wantErr := "dbus: not started"
|
||||
@@ -176,9 +151,9 @@ func testProxyFinaliseStartWaitCloseString(t *testing.T, useSandbox bool) {
|
||||
}
|
||||
|
||||
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 {
|
||||
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) {
|
||||
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 {
|
||||
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 {
|
||||
cmd.Stdout, cmd.Stderr = p.output, p.output
|
||||
}
|
||||
@@ -56,7 +53,7 @@ func (p *Proxy) Start() error {
|
||||
}
|
||||
|
||||
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
|
||||
} else {
|
||||
libPaths = ldd.Path(entries)
|
||||
@@ -69,15 +66,10 @@ func (p *Proxy) Start() error {
|
||||
z.SeccompFlags |= seccomp.AllowMultiarch
|
||||
z.SeccompPresets |= seccomp.PresetStrict
|
||||
z.Hostname = "hakurei-dbus"
|
||||
z.CommandContext = p.CommandContext
|
||||
if p.output != nil {
|
||||
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
|
||||
for _, name := range libPaths {
|
||||
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"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
@@ -37,10 +36,6 @@ type Proxy struct {
|
||||
useSandbox bool
|
||||
|
||||
name string
|
||||
CmdF func(any)
|
||||
|
||||
CommandContext func(ctx context.Context) (cmd *exec.Cmd)
|
||||
FilterF func([]byte) []byte
|
||||
|
||||
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 (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
)
|
||||
|
||||
func TestEnsure(t *testing.T) {
|
||||
@@ -60,11 +62,11 @@ func TestMkdirString(t *testing.T) {
|
||||
t.Run(tc.want, func(t *testing.T) {
|
||||
m := &Mkdir{
|
||||
et: tc.et,
|
||||
path: "/nonexistent",
|
||||
path: container.Nonexistent,
|
||||
perm: 0701,
|
||||
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 {
|
||||
t.Errorf("String() = %v, want %v", got, want)
|
||||
}
|
||||
|
||||
@@ -82,13 +82,18 @@
|
||||
jack.enable = true;
|
||||
};
|
||||
|
||||
virtualisation.qemu.options = [
|
||||
# Need to switch to a different GPU driver than the default one (-vga std) so that Sway can launch:
|
||||
"-vga none -device virtio-gpu-pci"
|
||||
virtualisation = {
|
||||
# Hopefully reduces spurious test failures:
|
||||
memorySize = 4096;
|
||||
|
||||
# Increase Go test compiler performance:
|
||||
"-smp 8"
|
||||
];
|
||||
qemu.options = [
|
||||
# Need to switch to a different GPU driver than the default one (-vga std) so that Sway can launch:
|
||||
"-vga none -device virtio-gpu-pci"
|
||||
|
||||
# Increase Go test compiler performance:
|
||||
"-smp 8"
|
||||
];
|
||||
};
|
||||
|
||||
environment.hakurei = {
|
||||
enable = true;
|
||||
@@ -127,6 +132,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" = {
|
||||
name = "pa-foot";
|
||||
identity = 2;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build testtool
|
||||
|
||||
/*
|
||||
Package sandbox provides utilities for checking sandbox outcome.
|
||||
|
||||
@@ -15,7 +17,6 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -40,13 +41,10 @@ type T struct {
|
||||
MountsPath string
|
||||
}
|
||||
|
||||
func (t *T) MustCheckFile(wantFilePath, markerPath string) {
|
||||
func (t *T) MustCheckFile(wantFilePath string) {
|
||||
var want *TestCase
|
||||
mustDecode(wantFilePath, &want)
|
||||
t.MustCheck(want)
|
||||
if _, err := os.Create(markerPath); err != nil {
|
||||
fatalf("cannot create success marker: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *T) MustCheck(want *TestCase) {
|
||||
@@ -165,31 +163,10 @@ func CheckFilter(pid int, want string) error {
|
||||
}()
|
||||
|
||||
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 {
|
||||
h.Write(b[:])
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build testtool
|
||||
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
lib: testProgram:
|
||||
system: lib: testProgram:
|
||||
let
|
||||
fs = mode: dir: data: {
|
||||
mode = lib.fromHexString mode;
|
||||
@@ -23,16 +23,21 @@ let
|
||||
;
|
||||
};
|
||||
|
||||
importTestCase =
|
||||
path:
|
||||
import path {
|
||||
inherit
|
||||
fs
|
||||
ent
|
||||
ignore
|
||||
system
|
||||
;
|
||||
};
|
||||
|
||||
callTestCase =
|
||||
path: identity:
|
||||
let
|
||||
tc = import path {
|
||||
inherit
|
||||
fs
|
||||
ent
|
||||
ignore
|
||||
;
|
||||
};
|
||||
tc = importTestCase path;
|
||||
in
|
||||
{
|
||||
name = "check-sandbox-${tc.name}";
|
||||
@@ -43,21 +48,30 @@ let
|
||||
device
|
||||
mapRealUid
|
||||
useCommonPaths
|
||||
userns
|
||||
;
|
||||
share = testProgram;
|
||||
packages = [ ];
|
||||
path = "${testProgram}/bin/hakurei-test";
|
||||
args = [
|
||||
"test"
|
||||
"-t"
|
||||
(toString (builtins.toFile "hakurei-${tc.name}-want.json" (builtins.toJSON tc.want)))
|
||||
"-s"
|
||||
tc.expectedFilter.${system}
|
||||
];
|
||||
};
|
||||
|
||||
testCaseName = name: "cat.gensokyo.hakurei.test." + name;
|
||||
in
|
||||
{
|
||||
${testCaseName "preset"} = callTestCase ./preset.nix 1;
|
||||
${testCaseName "tty"} = callTestCase ./tty.nix 2;
|
||||
${testCaseName "mapuid"} = callTestCase ./mapuid.nix 3;
|
||||
${testCaseName "device"} = callTestCase ./device.nix 4;
|
||||
apps = {
|
||||
${testCaseName "preset"} = callTestCase ./preset.nix 1;
|
||||
${testCaseName "tty"} = callTestCase ./tty.nix 2;
|
||||
${testCaseName "mapuid"} = callTestCase ./mapuid.nix 3;
|
||||
${testCaseName "device"} = callTestCase ./device.nix 4;
|
||||
${testCaseName "pdlike"} = callTestCase ./pdlike.nix 5;
|
||||
};
|
||||
|
||||
pd = importTestCase ./pd.nix;
|
||||
}
|
||||
|
||||
@@ -2,13 +2,35 @@
|
||||
fs,
|
||||
ent,
|
||||
ignore,
|
||||
system,
|
||||
}:
|
||||
let
|
||||
extraPaths = {
|
||||
x86_64-linux = {
|
||||
fd = "fd0";
|
||||
sr = {
|
||||
sr0 = fs "80001ff" null null;
|
||||
};
|
||||
};
|
||||
aarch64-linux = {
|
||||
fd = "mtdblock0";
|
||||
sr = { };
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
name = "device";
|
||||
tty = false;
|
||||
device = true;
|
||||
mapRealUid = false;
|
||||
useCommonPaths = true;
|
||||
userns = false;
|
||||
|
||||
# 0, PresetStrict
|
||||
expectedFilter = {
|
||||
x86_64-linux = "e880298df2bd6751d0040fc21bc0ed4c00f95dc0d7ba506c244d8b8cf6866dba8ef4a33296f287b66cccc1d78e97026597f84cc7dec1573e148960fbd35cd735";
|
||||
aarch64-linux = "79318538a3dc851314b6bd96f10d5861acb2aa7e13cb8de0619d0f6a76709d67f01ef3fd67e195862b02f9711e5b769bc4d1eb4fc0dfc41a723c89c968a93297";
|
||||
};
|
||||
|
||||
want = {
|
||||
env = [
|
||||
@@ -113,19 +135,21 @@
|
||||
} null;
|
||||
} null;
|
||||
sys = fs "800001c0" {
|
||||
block = fs "800001ed" {
|
||||
fd0 = 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;
|
||||
sr0 = fs "80001ff" null null;
|
||||
vda = fs "80001ff" null null;
|
||||
} null;
|
||||
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" {
|
||||
@@ -149,13 +173,6 @@
|
||||
} null;
|
||||
} null;
|
||||
".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" {
|
||||
".keep" = fs "80001ff" null "";
|
||||
home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null;
|
||||
@@ -178,15 +195,14 @@
|
||||
} null;
|
||||
} null;
|
||||
} null;
|
||||
run = fs "800001ed" { nscd = fs "800001ed" { } null; } null;
|
||||
cache = fs "800001ed" { private = fs "800001c0" null null; } null;
|
||||
} null;
|
||||
} null;
|
||||
|
||||
mount = [
|
||||
(ent "/sysroot" "/" "rw,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000004,gid=1000004")
|
||||
(ent "/sysroot" "/" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000004,gid=1000004")
|
||||
(ent "/" "/proc" "rw,nosuid,nodev,noexec,relatime" "proc" "proc" "rw")
|
||||
(ent "/" "/.hakurei" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000004,gid=1000004")
|
||||
(ent "/" "/.hakurei" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000004,gid=1000004")
|
||||
(ent "/" "/dev" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||
(ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666")
|
||||
(ent "/" "/dev/shm" "rw,nosuid,nodev" "tmpfs" "tmpfs" ignore)
|
||||
@@ -203,7 +219,7 @@
|
||||
(ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||
(ent "/var/cache" "/var/cache" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(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=1000004,gid=1000004")
|
||||
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000004,gid=1000004")
|
||||
(ent "/tmp/hakurei.1000/runtime/4" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent "/tmp/hakurei.1000/tmpdir/4" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent "/var/lib/hakurei/u0/a4" "/var/lib/hakurei/u0/a4" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
@@ -212,7 +228,6 @@
|
||||
(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=1000004,gid=1000004")
|
||||
];
|
||||
|
||||
seccomp = true;
|
||||
|
||||
@@ -2,13 +2,44 @@
|
||||
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 = "mapuid";
|
||||
tty = false;
|
||||
device = false;
|
||||
mapRealUid = true;
|
||||
useCommonPaths = true;
|
||||
userns = false;
|
||||
|
||||
# 0, PresetStrict
|
||||
expectedFilter = {
|
||||
x86_64-linux = "e880298df2bd6751d0040fc21bc0ed4c00f95dc0d7ba506c244d8b8cf6866dba8ef4a33296f287b66cccc1d78e97026597f84cc7dec1573e148960fbd35cd735";
|
||||
aarch64-linux = "79318538a3dc851314b6bd96f10d5861acb2aa7e13cb8de0619d0f6a76709d67f01ef3fd67e195862b02f9711e5b769bc4d1eb4fc0dfc41a723c89c968a93297";
|
||||
};
|
||||
|
||||
want = {
|
||||
env = [
|
||||
@@ -29,14 +60,7 @@
|
||||
bin = fs "800001ed" { sh = fs "80001ff" null null; } null;
|
||||
dev = fs "800001ed" {
|
||||
core = fs "80001ff" null null;
|
||||
dri = fs "800001ed" {
|
||||
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;
|
||||
dri = fs "800001ed" extraPaths.${system}."/dev/dri" null;
|
||||
fd = fs "80001ff" null null;
|
||||
full = fs "42001b6" null null;
|
||||
mqueue = fs "801001ff" { } null;
|
||||
@@ -137,19 +161,21 @@
|
||||
} null;
|
||||
} null;
|
||||
sys = fs "800001c0" {
|
||||
block = fs "800001ed" {
|
||||
fd0 = 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;
|
||||
sr0 = fs "80001ff" null null;
|
||||
vda = fs "80001ff" null null;
|
||||
} null;
|
||||
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" {
|
||||
@@ -173,13 +199,6 @@
|
||||
} null;
|
||||
} null;
|
||||
".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" {
|
||||
".keep" = fs "80001ff" null "";
|
||||
home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null;
|
||||
@@ -202,15 +221,14 @@
|
||||
} null;
|
||||
} null;
|
||||
} null;
|
||||
run = fs "800001ed" { nscd = fs "800001ed" { } null; } null;
|
||||
cache = fs "800001ed" { private = fs "800001c0" null null; } null;
|
||||
} null;
|
||||
} null;
|
||||
|
||||
mount = [
|
||||
(ent "/sysroot" "/" "rw,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000003,gid=1000003")
|
||||
(ent "/sysroot" "/" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000003,gid=1000003")
|
||||
(ent "/" "/proc" "rw,nosuid,nodev,noexec,relatime" "proc" "proc" "rw")
|
||||
(ent "/" "/.hakurei" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000003,gid=1000003")
|
||||
(ent "/" "/.hakurei" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000003,gid=1000003")
|
||||
(ent "/" "/dev" "rw,nosuid,nodev,relatime" "tmpfs" "devtmpfs" "rw,mode=755,uid=1000003,gid=1000003")
|
||||
(ent "/null" "/dev/null" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||
(ent "/zero" "/dev/zero" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||
@@ -231,7 +249,7 @@
|
||||
(ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||
(ent "/var/cache" "/var/cache" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(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=1000003,gid=1000003")
|
||||
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000003,gid=1000003")
|
||||
(ent "/tmp/hakurei.1000/runtime/3" "/run/user/1000" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent "/tmp/hakurei.1000/tmpdir/3" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent "/var/lib/hakurei/u0/a3" "/var/lib/hakurei/u0/a3" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
@@ -240,7 +258,6 @@
|
||||
(ent ignore "/run/user/1000/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent ignore "/run/user/1000/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
|
||||
(ent ignore "/run/user/1000/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=1000003,gid=1000003")
|
||||
];
|
||||
|
||||
seccomp = true;
|
||||
|
||||
198
test/sandbox/case/pd.nix
Normal file
198
test/sandbox/case/pd.nix
Normal file
@@ -0,0 +1,198 @@
|
||||
{
|
||||
fs,
|
||||
ent,
|
||||
ignore,
|
||||
...
|
||||
}:
|
||||
{
|
||||
# 0, PresetExt | PresetDenyDevel
|
||||
expectedFilter = {
|
||||
x86_64-linux = "c698b081ff957afe17a6d94374537d37f2a63f6f9dd75da7546542407a9e32476ebda3312ba7785d7f618542bcfaf27ca27dcc2dddba852069d28bcfe8cad39a";
|
||||
aarch64-linux = "433ce9b911282d6dcc8029319fb79b816b60d5a795ec8fc94344dd027614d68f023166a91bb881faaeeedd26e3d89474e141e5a69a97e93b8984ca8f14999980";
|
||||
};
|
||||
|
||||
want = {
|
||||
env = [
|
||||
"HOME=/var/lib/hakurei/u0/a0"
|
||||
"SHELL=/run/current-system/sw/bin/bash"
|
||||
"TERM=linux"
|
||||
"USER=u0_a0"
|
||||
"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;
|
||||
fd = fs "80001ff" null null;
|
||||
full = fs "42001b6" null null;
|
||||
kvm = 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_a0:x:65534:65534:Hakurei:/var/lib/hakurei/u0/a0:/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;
|
||||
home = fs "800001ed" { alice = fs "800001c0" null null; } null;
|
||||
lib64 = fs "800001ed" { "ld-linux-x86-64.so.2" = fs "80001ff" null null; } null;
|
||||
"lost+found" = fs "800001c0" null null;
|
||||
nix = fs "800001ed" {
|
||||
".ro-store" = fs "801001fd" null null;
|
||||
".rw-store" = fs "800001ed" null null;
|
||||
store = fs "801001fd" null null;
|
||||
var = fs "800001ed" {
|
||||
log = fs "800001ed" null null;
|
||||
nix = fs "800001ed" null null;
|
||||
} null;
|
||||
} null;
|
||||
proc = fs "8000016d" null null;
|
||||
root = fs "800001c0" null null;
|
||||
run = fs "800001ed" null null;
|
||||
srv = fs "800001ed" { } null;
|
||||
sys = fs "8000016d" null null;
|
||||
tmp = fs "800001f8" { } null;
|
||||
usr = fs "800001ed" { bin = fs "800001ed" { env = fs "80001ff" null null; } null; } null;
|
||||
var = fs "800001ed" null null;
|
||||
} null;
|
||||
|
||||
mount = [
|
||||
(ent "/sysroot" "/" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000000,gid=1000000")
|
||||
(ent "/bin" "/bin" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent "/home" "/home" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent "/lib64" "/lib64" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent "/lost+found" "/lost+found" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent "/nix" "/nix" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent "/" "/nix/.ro-store" "rw,nosuid,nodev,relatime" "9p" "nix-store" ignore)
|
||||
(ent "/" "/nix/.rw-store" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,mode=755")
|
||||
(ent "/" "/nix/store" "rw,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 "/" "/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 "/root" "/root" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent "/" "/run" "rw,nosuid,nodev" "tmpfs" "tmpfs" ignore)
|
||||
(ent "/" "/run/keys" "rw,nosuid,nodev,relatime" "ramfs" "ramfs" "rw,mode=750")
|
||||
(ent "/" "/run/credentials/systemd-journald.service" "ro,nosuid,nodev,noexec,relatime,nosymfollow" "tmpfs" "tmpfs" "rw,size=1024k,nr_inodes=1024,mode=700,noswap")
|
||||
(ent "/" "/run/wrappers" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
|
||||
(ent "/" "/run/credentials/getty@tty1.service" "ro,nosuid,nodev,noexec,relatime,nosymfollow" "tmpfs" "tmpfs" "rw,size=1024k,nr_inodes=1024,mode=700,noswap")
|
||||
(ent "/" "/run/user/1000" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
|
||||
(ent "/srv" "/srv" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent "/" "/sys" "rw,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
||||
(ent "/" "/sys/kernel/security" "rw,nosuid,nodev,noexec,relatime" "securityfs" "securityfs" "rw")
|
||||
(ent "/../../.." "/sys/fs/cgroup" "rw,nosuid,nodev,noexec,relatime" "cgroup2" "cgroup2" "rw,nsdelegate,memory_recursiveprot")
|
||||
(ent "/" "/sys/fs/pstore" "rw,nosuid,nodev,noexec,relatime" "pstore" "pstore" "rw")
|
||||
(ent "/" "/sys/fs/bpf" "rw,nosuid,nodev,noexec,relatime" "bpf" "bpf" "rw,mode=700")
|
||||
# systemd race: tracefs debugfs configfs fusectl
|
||||
(ent "/" ignore "rw,nosuid,nodev,noexec,relatime" ignore ignore "rw")
|
||||
(ent "/" ignore "rw,nosuid,nodev,noexec,relatime" ignore ignore "rw")
|
||||
(ent "/" ignore "rw,nosuid,nodev,noexec,relatime" ignore ignore "rw")
|
||||
(ent "/" ignore "rw,nosuid,nodev,noexec,relatime" ignore ignore "rw")
|
||||
(ent "/usr" "/usr" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent "/var" "/var" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent "/" "/proc" "rw,nosuid,nodev,noexec,relatime" "proc" "proc" "rw")
|
||||
(ent "/" "/.hakurei" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000000,gid=1000000")
|
||||
(ent "/" "/dev" "rw,nosuid,nodev,relatime" "tmpfs" "devtmpfs" "rw,mode=755,uid=1000000,gid=1000000")
|
||||
(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 "/kvm" "/dev/kvm" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||
(ent "/" "/run/nscd" "ro,nosuid,nodev,relatime" "tmpfs" "readonly" "ro,mode=755,uid=1000000,gid=1000000")
|
||||
(ent "/" "/run/user/1000" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=8k,mode=755,uid=1000000,gid=1000000")
|
||||
(ent "/" "/run/dbus" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=8k,mode=755,uid=1000000,gid=1000000")
|
||||
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000000,gid=1000000")
|
||||
(ent "/tmp/hakurei.1000/runtime/0" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent "/tmp/hakurei.1000/tmpdir/0" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent "/var/lib/hakurei/u0/a0" "/var/lib/hakurei/u0/a0" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000000,gid=1000000")
|
||||
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000000,gid=1000000")
|
||||
];
|
||||
|
||||
seccomp = true;
|
||||
};
|
||||
}
|
||||
265
test/sandbox/case/pdlike.nix
Normal file
265
test/sandbox/case/pdlike.nix
Normal file
@@ -0,0 +1,265 @@
|
||||
{
|
||||
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;
|
||||
} null;
|
||||
} null;
|
||||
|
||||
mount = [
|
||||
(ent "/sysroot" "/" "ro,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" "ephemeral" "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" "ephemeral" "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")
|
||||
];
|
||||
|
||||
seccomp = true;
|
||||
};
|
||||
}
|
||||
@@ -2,13 +2,44 @@
|
||||
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 = "preset";
|
||||
tty = false;
|
||||
device = false;
|
||||
mapRealUid = false;
|
||||
useCommonPaths = false;
|
||||
userns = false;
|
||||
|
||||
# 0, PresetStrict
|
||||
expectedFilter = {
|
||||
x86_64-linux = "e880298df2bd6751d0040fc21bc0ed4c00f95dc0d7ba506c244d8b8cf6866dba8ef4a33296f287b66cccc1d78e97026597f84cc7dec1573e148960fbd35cd735";
|
||||
aarch64-linux = "79318538a3dc851314b6bd96f10d5861acb2aa7e13cb8de0619d0f6a76709d67f01ef3fd67e195862b02f9711e5b769bc4d1eb4fc0dfc41a723c89c968a93297";
|
||||
};
|
||||
|
||||
want = {
|
||||
env = [
|
||||
@@ -29,14 +60,7 @@
|
||||
bin = fs "800001ed" { sh = fs "80001ff" null null; } null;
|
||||
dev = fs "800001ed" {
|
||||
core = fs "80001ff" null null;
|
||||
dri = fs "800001ed" {
|
||||
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;
|
||||
dri = fs "800001ed" extraPaths.${system}."/dev/dri" null;
|
||||
fd = fs "80001ff" null null;
|
||||
full = fs "42001b6" null null;
|
||||
mqueue = fs "801001ff" { } null;
|
||||
@@ -137,19 +161,21 @@
|
||||
} null;
|
||||
} null;
|
||||
sys = fs "800001c0" {
|
||||
block = fs "800001ed" {
|
||||
fd0 = 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;
|
||||
sr0 = fs "80001ff" null null;
|
||||
vda = fs "80001ff" null null;
|
||||
} null;
|
||||
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" {
|
||||
@@ -173,13 +199,6 @@
|
||||
} null;
|
||||
} null;
|
||||
".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" {
|
||||
".keep" = fs "80001ff" null "";
|
||||
home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null;
|
||||
@@ -202,14 +221,13 @@
|
||||
} null;
|
||||
} null;
|
||||
} null;
|
||||
run = fs "800001ed" { nscd = fs "800001ed" { } null; } null;
|
||||
} null;
|
||||
} null;
|
||||
|
||||
mount = [
|
||||
(ent "/sysroot" "/" "rw,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000001,gid=1000001")
|
||||
(ent "/sysroot" "/" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000001,gid=1000001")
|
||||
(ent "/" "/proc" "rw,nosuid,nodev,noexec,relatime" "proc" "proc" "rw")
|
||||
(ent "/" "/.hakurei" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000001,gid=1000001")
|
||||
(ent "/" "/.hakurei" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000001,gid=1000001")
|
||||
(ent "/" "/dev" "rw,nosuid,nodev,relatime" "tmpfs" "devtmpfs" "rw,mode=755,uid=1000001,gid=1000001")
|
||||
(ent "/null" "/dev/null" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||
(ent "/zero" "/dev/zero" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||
@@ -229,7 +247,7 @@
|
||||
(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=1000001,gid=1000001")
|
||||
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000001,gid=1000001")
|
||||
(ent "/tmp/hakurei.1000/runtime/1" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent "/tmp/hakurei.1000/tmpdir/1" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent "/var/lib/hakurei/u0/a1" "/var/lib/hakurei/u0/a1" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
@@ -238,7 +256,6 @@
|
||||
(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=1000001,gid=1000001")
|
||||
];
|
||||
|
||||
seccomp = true;
|
||||
|
||||
@@ -2,13 +2,44 @@
|
||||
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 = "tty";
|
||||
tty = true;
|
||||
device = false;
|
||||
mapRealUid = false;
|
||||
useCommonPaths = true;
|
||||
userns = false;
|
||||
|
||||
# 0, PresetExt | PresetDenyNS | PresetDenyDevel
|
||||
expectedFilter = {
|
||||
x86_64-linux = "0b76007476c1c9e25dbf674c29fdf609a1656a70063e49327654e1b5360ad3da06e1a3e32bf80e961c5516ad83d4b9e7e9bde876a93797e27627d2555c25858b";
|
||||
aarch64-linux = "cf1f4dc87436ba8ec95d268b663a6397bb0b4a5ac64d8557e6cc529d8b0f6f65dad3a92b62ed29d85eee9c6dde1267757a4d0f86032e8a45ca1bceadfa34cf5e";
|
||||
};
|
||||
|
||||
want = {
|
||||
env = [
|
||||
@@ -30,14 +61,7 @@
|
||||
dev = fs "800001ed" {
|
||||
console = fs "4200190" null null;
|
||||
core = fs "80001ff" null null;
|
||||
dri = fs "800001ed" {
|
||||
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;
|
||||
dri = fs "800001ed" extraPaths.${system}."/dev/dri" null;
|
||||
fd = fs "80001ff" null null;
|
||||
full = fs "42001b6" null null;
|
||||
mqueue = fs "801001ff" { } null;
|
||||
@@ -138,19 +162,21 @@
|
||||
} null;
|
||||
} null;
|
||||
sys = fs "800001c0" {
|
||||
block = fs "800001ed" {
|
||||
fd0 = 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;
|
||||
sr0 = fs "80001ff" null null;
|
||||
vda = fs "80001ff" null null;
|
||||
} null;
|
||||
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" {
|
||||
@@ -174,13 +200,6 @@
|
||||
} null;
|
||||
} null;
|
||||
".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" {
|
||||
".keep" = fs "80001ff" null "";
|
||||
home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null;
|
||||
@@ -203,15 +222,14 @@
|
||||
} null;
|
||||
} null;
|
||||
} null;
|
||||
run = fs "800001ed" { nscd = fs "800001ed" { } null; } null;
|
||||
cache = fs "800001ed" { private = fs "800001c0" null null; } null;
|
||||
} null;
|
||||
} null;
|
||||
|
||||
mount = [
|
||||
(ent "/sysroot" "/" "rw,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000002,gid=1000002")
|
||||
(ent "/sysroot" "/" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000002,gid=1000002")
|
||||
(ent "/" "/proc" "rw,nosuid,nodev,noexec,relatime" "proc" "proc" "rw")
|
||||
(ent "/" "/.hakurei" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000002,gid=1000002")
|
||||
(ent "/" "/.hakurei" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000002,gid=1000002")
|
||||
(ent "/" "/dev" "rw,nosuid,nodev,relatime" "tmpfs" "devtmpfs" "rw,mode=755,uid=1000002,gid=1000002")
|
||||
(ent "/null" "/dev/null" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||
(ent "/zero" "/dev/zero" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||
@@ -233,7 +251,7 @@
|
||||
(ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||
(ent "/var/cache" "/var/cache" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(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=1000002,gid=1000002")
|
||||
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=4k,mode=755,uid=1000002,gid=1000002")
|
||||
(ent "/tmp/hakurei.1000/runtime/2" "/run/user/65534" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent "/tmp/hakurei.1000/tmpdir/2" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
(ent "/var/lib/hakurei/u0/a2" "/var/lib/hakurei/u0/a2" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||
@@ -242,7 +260,6 @@
|
||||
(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=1000002,gid=1000002")
|
||||
];
|
||||
|
||||
seccomp = true;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
}:
|
||||
let
|
||||
testProgram = pkgs.callPackage ./tool/package.nix { inherit (config.environment.hakurei.package) version; };
|
||||
testCases = import ./case pkgs.system lib testProgram;
|
||||
in
|
||||
{
|
||||
users.users = {
|
||||
@@ -26,6 +27,13 @@ in
|
||||
systemPackages = [
|
||||
# For checking seccomp outcome:
|
||||
testProgram
|
||||
|
||||
# For checking pd outcome:
|
||||
(pkgs.writeShellScriptBin "check-sandbox-pd" ''
|
||||
hakurei -v run hakurei-test \
|
||||
-t ${toString (builtins.toFile "hakurei-pd-want.json" (builtins.toJSON testCases.pd.want))} \
|
||||
-s ${testCases.pd.expectedFilter.${pkgs.system}} "$@"
|
||||
'')
|
||||
];
|
||||
|
||||
variables = {
|
||||
@@ -75,6 +83,6 @@ in
|
||||
}
|
||||
];
|
||||
|
||||
apps = import ./case lib testProgram;
|
||||
inherit (testCases) apps;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build testtool
|
||||
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build testtool
|
||||
|
||||
package sandbox_test
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build testtool
|
||||
|
||||
package sandbox
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build testtool
|
||||
|
||||
package sandbox_test
|
||||
|
||||
import (
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user