carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "errors"
5 "fmt"
6 "io"
7 "os"
8 "os/user"
9 "path/filepath"
10 "strconv"
11)
12
13// The floor gate: `carlos system floor-allows-prev <floor file> <verdict
14// file>`, which the two system units (carlos-system-update.service and
15// carlos-system-recover.service) run as root, from carlos.prev, before
16// they let carlos.prev run a recovery against an armed floor (moves spec
17// §11; see movesfloor.go for the files). It replaces a `cmp -s` in the
18// units' shell, which reopened the agent's floor file after the shell had
19// tested it: between the two the agent, which owns /data, could put a
20// symlink there (root then compared a file of the agent's choosing, a
21// one-bit oracle) or a FIFO (root blocked until the unit timed out).
22//
23// The exit code is the answer: 0 when the verdict names the floor byte for
24// byte, 1 for everything else (a mismatch, a refused or unreadable file,
25// a misuse). The shell treats them all alike, so every failure here is
26// fail-closed: the current binary runs, and its own Go check decides.
27//
28// carlos.prev runs the gate, not carlos, because the units pick carlos.prev
29// on the premise that carlos is the candidate that did not work, and a
30// gate in carlos would fail with it. Running carlos.prev for the gate is no
31// new trust: root already runs `carlos.prev version` when a floor is armed
32// (rootJudgePrev). A carlos.prev too old to have the verb exits non-zero,
33// which the units read as a mismatch, so the current binary runs.
34
35// floorGateMaxBytes bounds both files. Each holds one release line,
36// "vMAJOR.MINOR.PATCH\n" (writeFloorFiles, judgePrevWith).
37const floorGateMaxBytes = 256
38
39var (
40 errFloorGateMismatch = errors.New("the verdict on carlos.prev does not name the floor on this box")
41 errFloorGateUnsafe = errors.New("is not a regular file with one link, at most 256 bytes, owned by an expected account")
42 errFloorGateUsage = errors.New("floor-allows-prev takes two absolute paths: the floor file and the verdict file")
43)
44
45// floorGateUnsafe is the refusal of a file the gate will not read (MV-N317),
46// said whole in one format so the copy gate can read it. It wraps
47// errFloorGateUnsafe for errors.Is, and its text is the sentence alone.
48func floorGateUnsafe(path string) error {
49 return &floorGateUnsafeError{
50 msg: fmt.Sprintf("%s is not a regular file with one link, at most 256 bytes, owned by an expected account", path),
51 }
52}
53
54type floorGateUnsafeError struct{ msg string }
55
56func (e *floorGateUnsafeError) Error() string { return e.msg }
57func (e *floorGateUnsafeError) Unwrap() error { return errFloorGateUnsafe }
58
59// RunSystemFloorAllowsPrev is the verb's entry point. The floor file may be
60// owned by root (root's apply writes it) or by the service account (the
61// agent's refresh writes it); the verdict only by root.
62func RunSystemFloorAllowsPrev(args []string) {
63 os.Exit(floorGateMain(args, floorGateFloorOwners(), []uint32{0}, os.Stderr))
64}
65
66// floorGateMain checks args, runs the gate and says why on stderr when
67// carlos.prev may not run. It returns the exit code.
68func floorGateMain(args []string, floorOwners, verdictOwners []uint32, stderr io.Writer) int {
69 err := errFloorGateUsage
70 if len(args) == 2 && args[0] == "--root" {
71 err = rootFloorAllowsPrev(args[1])
72 }
73 if len(args) == 2 && filepath.IsAbs(args[0]) && filepath.IsAbs(args[1]) {
74 err = floorAllowsPrev(args[0], args[1], floorOwners, verdictOwners)
75 }
76 if err != nil {
77 fmt.Fprintf(stderr, "moves protocol floor: carlos.prev may not run: %v\n", err)
78 return 1
79 }
80 return 0
81}
82
83// floorGateFloorOwners is root and, when the box has one, the service
84// account. A box where the account cannot be looked up accepts root alone.
85func floorGateFloorOwners() []uint32 {
86 owners := []uint32{0}
87 if u, err := user.Lookup(serviceUser); err == nil {
88 if uid, err := strconv.ParseUint(u.Uid, 10, 32); err == nil {
89 owners = append(owners, uint32(uid))
90 }
91 }
92 return owners
93}
94