carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1// Package instancefs is how the agent opens anything inside an instance's
2// own directory. Every exec-backed instance runs as the agent's uid, and
3// its directory is writable by it, so any path the agent opens there is a
4// request the instance makes on the agent's behalf: a symlink at
5// <host>.env pointing at /data/litestream.env would hand the instance the
6// bucket credentials as its own environment (isolated-exec design §1.3).
7//
8// The kernel closes that for every reader at once with the nosymfollow
9// mount option on the instance trees (design §3.2). This package is the
10// belt to that brace: it opens with openat2(RESOLVE_BENEATH |
11// RESOLVE_NO_SYMLINKS | RESOLVE_NO_MAGICLINKS), so a symlink is refused
12// with a named error even on a box whose mount is missing the option, and
13// it writes through O_EXCL temp files and rename, so a planted symlink at
14// a destination is replaced as a directory entry rather than written
15// through.
16//
17// Nothing here is Linux-specific in its contract; the implementations
18// are, and the !linux build returns errors.ErrUnsupported so the CLI still
19// builds on a Mac (AGENTS.md's cross-compile gate).
20package instancefs
21
22import (
23 "errors"
24 "fmt"
25 "io"
26 "os"
27 "path/filepath"
28 "strings"
29)
30
31// ErrSymlink is returned when any component of the path is a symlink.
32var ErrSymlink = errors.New("instancefs: path contains a symlink")
33
34// ErrEscapes is returned for a name that is absolute or reaches above dir.
35var ErrEscapes = errors.New("instancefs: path escapes its directory")
36
37// ErrNotRegular is returned when a read-only open resolves to anything
38// other than a regular file: a FIFO with no writer blocks open(2)
39// indefinitely, and a directory or device is never a legitimate target for
40// the files this package reads on a tenant's behalf.
41var ErrNotRegular = errors.New("instancefs: not a regular file")
42
43// ErrDirUnopened wraps whatever kept OpenBeneathOrAbsent from opening its
44// directory: never evidence that the name is absent.
45var ErrDirUnopened = errors.New("instancefs: the directory could not be opened")
46
47// ErrNotDir is returned when OpenDirBeneath resolves to anything other
48// than a directory: a file, or a FIFO, which it never waits on.
49var ErrNotDir = errors.New("instancefs: not a directory")
50
51// ErrOpenat2Unavailable is what SelfCheck returns when this process cannot
52// make the openat2 call every function here is built on. The one cause
53// seen in practice is the unit's own hardening: systemd's RestrictSUIDSGID=
54// cannot read the mode inside open_how, so it refuses openat2 outright with
55// ENOSYS (#126, first roll). A kernel before 5.6 answers the same way.
56var ErrOpenat2Unavailable = errors.New("instancefs: openat2 is refused in this process (ENOSYS): the unit carries RestrictSUIDSGID=, or the kernel predates 5.6")
57
58// DirStat is what EnsureDirBeneath and StatDir found: the mode in
59// os.FileMode form (setgid included) and the owning uid and gid.
60type DirStat struct {
61 Mode os.FileMode
62 UID, GID int
63}
64
65// cleanRel validates name as a relative path that stays inside dir.
66func cleanRel(name string) (string, error) {
67 if name == "" || filepath.IsAbs(name) {
68 return "", ErrEscapes
69 }
70 c := filepath.Clean(name)
71 if c == ".." || strings.HasPrefix(c, "../") {
72 return "", ErrEscapes
73 }
74 return c, nil
75}
76
77// bareName validates name as exactly one path component: the directory
78// operations act on one entry beneath an fd, and a name carrying a slash
79// would resolve through intermediate components that fd never vetted.
80func bareName(name string) error {
81 if name == "" || name == "." || name == ".." || strings.Contains(name, "/") {
82 return ErrEscapes
83 }
84 return nil
85}
86
87// ReadBeneath is OpenBeneath followed by reading the whole file.
88func ReadBeneath(dir, name string) ([]byte, error) {
89 f, err := OpenBeneath(dir, name, os.O_RDONLY, 0)
90 if err != nil {
91 return nil, err
92 }
93 defer f.Close()
94 return io.ReadAll(f)
95}
96
97// ErrTooLarge is returned by ReadBeneathLimit when the file exceeds max.
98var ErrTooLarge = errors.New("instancefs: file larger than the limit")
99
100// ReadBeneathLimit is ReadBeneath with a size cap. Files in an instance
101// directory are tenant-writable, and some are read by the agent outside the
102// tenant's memory wall (the exit record, the launch file), so no read there
103// is unbounded.
104func ReadBeneathLimit(dir, name string, max int64) ([]byte, error) {
105 f, err := OpenBeneath(dir, name, os.O_RDONLY, 0)
106 if err != nil {
107 return nil, err
108 }
109 defer f.Close()
110 return readLimit(f, filepath.Join(dir, name), max)
111}
112
113// ReadBeneathLimitOrAbsent is ReadBeneathLimit with absence decided by
114// OpenBeneathOrAbsent: absent only when name was not found beneath the one
115// descriptor dir was opened as. Every other failure, a dir that would not
116// open included, is an error.
117func ReadBeneathLimitOrAbsent(dir, name string, max int64) ([]byte, bool, error) {
118 f, absent, err := OpenBeneathOrAbsent(dir, name)
119 if err != nil || absent {
120 return nil, absent, err
121 }
122 defer f.Close()
123 b, err := readLimit(f, filepath.Join(dir, name), max)
124 return b, false, err
125}
126
127// readLimit reads f whole, refusing more than max bytes.
128func readLimit(f *os.File, path string, max int64) ([]byte, error) {
129 b, err := io.ReadAll(io.LimitReader(f, max+1))
130 if err != nil {
131 return nil, err
132 }
133 if int64(len(b)) > max {
134 return nil, fmt.Errorf("%w: %s over %d bytes", ErrTooLarge, path, max)
135 }
136 return b, nil
137}
138