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 "sync"
13 "testing"
14
15 "bnema.dev/ermite-runner/internal/cmdrun"
16 "bnema.dev/ermite-runner/internal/netpolicy"
17)
18
19// NoMountRunner runs real commands except mount and umount, which succeed
20// without doing anything: loop mounts need root, and the tests that use it
21// replace them by plain directories (mkfs still runs).
22type NoMountRunner struct{}
23
24// Run implements cmdrun.Runner.
25func (NoMountRunner) Run(ctx context.Context, in io.Reader, name string, args ...string) ([]byte, error) {
26 if name == "mount" || name == "umount" {
27 return nil, nil
28 }
29 return cmdrun.Exec{}.Run(ctx, in, name, args...)
30}
31
32// WriteStaticELF writes a minimal statically linked ELF64 file (one PT_LOAD, no
33// PT_INTERP): enough for sandbox.CheckStatic, never executed.
34func WriteStaticELF(t testing.TB, path string) {
35 t.Helper()
36 var b [64 + 56]byte
37 copy(b[:], "\x7fELF")
38 b[4], b[5], b[6] = 2, 1, 1 // 64-bit, little endian, version
39 binary.LittleEndian.PutUint16(b[16:], 2) // ET_EXEC
40 binary.LittleEndian.PutUint16(b[18:], 62) // x86-64
41 binary.LittleEndian.PutUint32(b[20:], 1)
42 binary.LittleEndian.PutUint64(b[32:], 64) // e_phoff
43 binary.LittleEndian.PutUint16(b[52:], 64) // e_ehsize
44 binary.LittleEndian.PutUint16(b[54:], 56) // e_phentsize
45 binary.LittleEndian.PutUint16(b[56:], 1) // e_phnum
46 binary.LittleEndian.PutUint32(b[64:], 1) // PT_LOAD
47 if err := os.WriteFile(path, b[:], 0o755); err != nil {
48 t.Fatal(err)
49 }
50}
51
52// Plumbing is a netsetup.Plumbing that records what is attached.
53type Plumbing struct {
54 mu sync.Mutex
55 Attached map[string]netpolicy.Guest
56 Events []string
57 Resets int
58 FailNext error
59}
60
61// NewPlumbing returns an empty Plumbing.
62func NewPlumbing() *Plumbing { return &Plumbing{Attached: map[string]netpolicy.Guest{}} }
63
64// EnableForwarding implements netsetup.Plumbing.
65func (p *Plumbing) EnableForwarding() error { return nil }
66
67// Attach implements netsetup.Plumbing.
68func (p *Plumbing) Attach(_ context.Context, g netpolicy.Guest) error {
69 p.mu.Lock()
70 defer p.mu.Unlock()
71 if p.FailNext != nil {
72 err := p.FailNext
73 p.FailNext = nil
74 return err
75 }
76 p.Attached[g.Iface] = g
77 p.Events = append(p.Events, "attach "+g.Iface)
78 return nil
79}
80
81// Detach implements netsetup.Plumbing.
82func (p *Plumbing) Detach(_ context.Context, iface string, _ netip.Addr) error {
83 p.mu.Lock()
84 defer p.mu.Unlock()
85 delete(p.Attached, iface)
86 p.Events = append(p.Events, "detach "+iface)
87 return nil
88}
89
90// Reset implements netsetup.Plumbing.
91func (p *Plumbing) Reset(context.Context) error {
92 p.mu.Lock()
93 defer p.mu.Unlock()
94 p.Resets++
95 p.Attached = map[string]netpolicy.Guest{}
96 return nil
97}
98
99// Close implements netsetup.Plumbing.
100func (p *Plumbing) Close() {}
101
102// Live returns the number of attached guests.
103func (p *Plumbing) Live() int {
104 p.mu.Lock()
105 defer p.mu.Unlock()
106 return len(p.Attached)
107}