1// SPDX-License-Identifier: MIT
2
3// Package sandboxtest has helpers shared by the tests of the sandbox drivers.
4package sandboxtest
5
6import (
7 "context"
8 "encoding/binary"
9 "io"
10 "net/netip"
11 "os"
12 "path/filepath"
13 "sync"
14 "testing"
15
16 "bnema.dev/ermite-runner/internal/cmdrun"
17 "bnema.dev/ermite-runner/internal/netpolicy"
18 "bnema.dev/ermite-runner/internal/sandbox"
19)
20
21// NoMountRunner runs real commands except mount and umount, which succeed
22// without doing anything: loop mounts need root, and the tests that use it
23// replace them by plain directories (mkfs still runs).
24type NoMountRunner struct{}
25
26// Run implements cmdrun.Runner.
27func (NoMountRunner) Run(ctx context.Context, in io.Reader, name string, args ...string) ([]byte, error) {
28 if name == "mount" || name == "umount" {
29 return nil, nil
30 }
31 return cmdrun.Exec{}.Run(ctx, in, name, args...)
32}
33
34// WriteStaticELF writes a minimal statically linked ELF64 file (one PT_LOAD, no
35// PT_INTERP): enough for sandbox.CheckStatic, never executed.
36func WriteStaticELF(t testing.TB, path string) {
37 t.Helper()
38 var b [64 + 56]byte
39 copy(b[:], "\x7fELF")
40 b[4], b[5], b[6] = 2, 1, 1 // 64-bit, little endian, version
41 binary.LittleEndian.PutUint16(b[16:], 2) // ET_EXEC
42 binary.LittleEndian.PutUint16(b[18:], 62) // x86-64
43 binary.LittleEndian.PutUint32(b[20:], 1)
44 binary.LittleEndian.PutUint64(b[32:], 64) // e_phoff
45 binary.LittleEndian.PutUint16(b[52:], 64) // e_ehsize
46 binary.LittleEndian.PutUint16(b[54:], 56) // e_phentsize
47 binary.LittleEndian.PutUint16(b[56:], 1) // e_phnum
48 binary.LittleEndian.PutUint32(b[64:], 1) // PT_LOAD
49 if err := os.WriteFile(path, b[:], 0o755); err != nil {
50 t.Fatal(err)
51 }
52}
53
54// Plumbing is a netsetup.Plumbing that records what is attached.
55type Plumbing struct {
56 mu sync.Mutex
57 Attached map[string]netpolicy.Guest
58 Events []string
59 Resets int
60 FailNext error
61}
62
63// NewPlumbing returns an empty Plumbing.
64func NewPlumbing() *Plumbing { return &Plumbing{Attached: map[string]netpolicy.Guest{}} }
65
66// EnableForwarding implements netsetup.Plumbing.
67func (p *Plumbing) EnableForwarding() error { return nil }
68
69// Attach implements netsetup.Plumbing.
70func (p *Plumbing) Attach(_ context.Context, g netpolicy.Guest) error {
71 p.mu.Lock()
72 defer p.mu.Unlock()
73 if p.FailNext != nil {
74 err := p.FailNext
75 p.FailNext = nil
76 return err
77 }
78 p.Attached[g.Iface] = g
79 p.Events = append(p.Events, "attach "+g.Iface)
80 return nil
81}
82
83// Detach implements netsetup.Plumbing.
84func (p *Plumbing) Detach(_ context.Context, iface string, _ netip.Addr) error {
85 p.mu.Lock()
86 defer p.mu.Unlock()
87 delete(p.Attached, iface)
88 p.Events = append(p.Events, "detach "+iface)
89 return nil
90}
91
92// Reset implements netsetup.Plumbing.
93func (p *Plumbing) Reset(context.Context) error {
94 p.mu.Lock()
95 defer p.mu.Unlock()
96 p.Resets++
97 p.Attached = map[string]netpolicy.Guest{}
98 return nil
99}
100
101// Close implements netsetup.Plumbing.
102func (p *Plumbing) Close() {}
103
104// Live returns the number of attached guests.
105func (p *Plumbing) Live() int {
106 p.mu.Lock()
107 defer p.mu.Unlock()
108 return len(p.Attached)
109}
110
111// Externals is a fixed, replaceable sandbox.ExternalsSource.
112type Externals struct {
113 mu sync.Mutex
114 set []sandbox.External
115}
116
117// Set replaces the set the drivers see.
118func (e *Externals) Set(set ...sandbox.External) { e.mu.Lock(); e.set = set; e.mu.Unlock() }
119
120// Externals implements sandbox.ExternalsSource.
121func (e *Externals) Externals() []sandbox.External {
122 e.mu.Lock()
123 defer e.mu.Unlock()
124 return append([]sandbox.External(nil), e.set...)
125}
126
127// MakeExternal creates a host directory holding bin/node with content and
128// returns it as an external called name.
129func MakeExternal(t testing.TB, name, id, content string) sandbox.External {
130 t.Helper()
131 dir := filepath.Join(t.TempDir(), name)
132 if err := os.MkdirAll(filepath.Join(dir, "bin"), 0o755); err != nil {
133 t.Fatal(err)
134 }
135 if err := os.WriteFile(filepath.Join(dir, "bin", "node"), []byte(content), 0o755); err != nil {
136 t.Fatal(err)
137 }
138 if err := os.Symlink("bin/node", filepath.Join(dir, "nodejs")); err != nil {
139 t.Fatal(err)
140 }
141 return sandbox.External{Name: name, ID: id, Dir: dir}
142}