carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "encoding/binary"
5 "errors"
6 "os"
7 "os/exec"
8 "path/filepath"
9 "testing"
10)
11
12// buildStampedProgram builds, for goarch, a real program in a module named
13// github.com/carlosframework/platform whose internal/version package holds a
14// Build string variable, the shape carlos and cmd/console have, with
15// ldflags as given.
16func buildStampedProgram(t *testing.T, goarch, ldflags string) []byte {
17 t.Helper()
18 dir := t.TempDir()
19 for name, body := range map[string]string{
20 "go.mod": "module github.com/carlosframework/platform\n\ngo 1.21\n",
21 "internal/version/version.go": "package version\n\nvar Build = \"dev\"\n",
22 "main.go": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/carlosframework/platform/internal/version\"\n)\n\nfunc main() { fmt.Println(version.Build) }\n",
23 } {
24 if err := os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0o755); err != nil {
25 t.Fatal(err)
26 }
27 if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0o644); err != nil {
28 t.Fatal(err)
29 }
30 }
31 out := filepath.Join(dir, "bin")
32 args := []string{"build", "-o", out}
33 if ldflags != "" {
34 args = append(args, "-ldflags", ldflags)
35 }
36 cmd := exec.Command("go", append(args, ".")...)
37 cmd.Dir = dir
38 cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH="+goarch, "CGO_ENABLED=0", "GOFLAGS=-buildvcs=false", "GOWORK=off", "GOPROXY=off")
39 if output, err := cmd.CombinedOutput(); err != nil {
40 t.Fatal(string(output), err)
41 }
42 b, err := os.ReadFile(out)
43 if err != nil {
44 t.Fatal(err)
45 }
46 return b
47}
48
49// ArtifactBuild reads the -X version.Build flag a real linux-arm64 build
50// records, for a program that has the version.Build variable it stamps. It
51// reads the recorded flag; it does not establish the linked value
52// (controller ruling F2: owed before production cohorts, trusted build
53// attestation tied to the artefact digest).
54func TestArtifactBuildReadsTheRecordedStampFlag(t *testing.T) {
55 if testing.Short() {
56 t.Skip("builds Go binaries")
57 }
58 for ldflags, want := range map[string]string{
59 "-s -w -X " + BuildStampFlag + "=v0.28.2": "v0.28.2",
60 "-s -w -X " + BuildStampFlag + "=26065378": "26065378",
61 // Unstripped: Go compresses its DWARF sections (SHF_COMPRESSED), which
62 // the bounded pre-check refuses. Release and console builds use -s -w.
63 "-X " + BuildStampFlag + "=v0.28.2": "",
64 "": "",
65 "-s -w": "",
66 "-s -w -X " + BuildStampFlag + "=v1 -X " + BuildStampFlag + "=v2": "",
67 "-s -w -X=" + BuildStampFlag + "=v0.28.2": "",
68 "-s -w -X github.com/carlosframework/platform/internal/version.BuildX=v0.28.2": "",
69 "-X github.com/carlosframework/platform/internal/other.Build=v0.28.2 -s -w": "",
70 } {
71 if got := ArtifactBuild(buildStampedProgram(t, "arm64", ldflags)); got != want {
72 t.Errorf("ldflags %q: stamp %q, want %q", ldflags, got, want)
73 }
74 }
75 // linux-amd64 is outside the bounded pre-check, so it has no stamp.
76 if got := ArtifactBuild(buildStampedProgram(t, "amd64", "-s -w -X "+BuildStampFlag+"=v0.28.2")); got != "" {
77 t.Fatal("stamp read from a non-arm64 binary", got)
78 }
79 if ArtifactBuild([]byte("not a binary")) != "" {
80 t.Fatal("stamp from non-binary")
81 }
82}
83
84// elfMutate returns a copy of a real linux-arm64 build with change applied
85// to it, given its section and program header offsets.
86func elfMutate(b []byte, change func(b []byte, shoff, phoff uint64)) []byte {
87 c := append([]byte(nil), b...)
88 change(c, binary.LittleEndian.Uint64(c[40:]), binary.LittleEndian.Uint64(c[32:]))
89 return c
90}
91
92// elfShstrtabCompressed sets SHF_COMPRESSED on the section-name table of a
93// real linux-arm64 build: the shape whose decompression Go's ELF reader
94// performs during parsing, unbounded by the file size.
95func elfShstrtabCompressed(b []byte) []byte {
96 return elfMutate(b, func(c []byte, shoff, _ uint64) {
97 idx := uint64(binary.LittleEndian.Uint16(c[62:]))
98 h := c[shoff+idx*64:]
99 binary.LittleEndian.PutUint64(h[8:], binary.LittleEndian.Uint64(h[8:])|0x800)
100 })
101}
102
103// elfOversizedShnum claims 65000 section headers in a real build.
104func elfOversizedShnum(b []byte) []byte {
105 return elfMutate(b, func(c []byte, _, _ uint64) { binary.LittleEndian.PutUint16(c[60:], 65000) })
106}
107
108// elfRepeatedName appends a name table holding one name of size bytes
109// to a real build, points its section-name table at it and every section's
110// name at that one name: the shape debug/elf would copy once per section
111// (Astra's re-review of F1).
112func elfRepeatedName(b []byte, size int) []byte {
113 c := append(append([]byte(nil), b...), make([]byte, size+1)...)
114 for i := len(b); i < len(b)+size; i++ {
115 c[i] = 'a'
116 }
117 le := binary.LittleEndian
118 shoff, shnum, idx := le.Uint64(c[40:]), uint64(le.Uint16(c[60:])), uint64(le.Uint16(c[62:]))
119 h := c[shoff+idx*64:]
120 le.PutUint64(h[24:], uint64(len(b)))
121 le.PutUint64(h[32:], uint64(size+1))
122 for i := uint64(0); i < shnum; i++ {
123 le.PutUint32(c[shoff+i*64:], 0)
124 }
125 return c
126}
127
128func TestCheckELFBoundsRefusesUnboundedMetadata(t *testing.T) {
129 if testing.Short() {
130 t.Skip("builds Go binaries")
131 }
132 real := buildStampedProgram(t, "arm64", "-s -w -X "+BuildStampFlag+"=v0.28.2")
133 if err := CheckELFBounds(real); err != nil {
134 t.Fatal("a real stripped linux-arm64 build refused", err)
135 }
136 // A 256-byte name, repeated, is inside the caps: 256 x 256 bytes at most.
137 if err := CheckELFBounds(elfRepeatedName(real, 256)); err != nil {
138 t.Fatal("a name at the cap refused", err)
139 }
140 le := binary.LittleEndian
141 for name, crafted := range map[string][]byte{
142 "compressed section-name table": elfShstrtabCompressed(real),
143 "oversized shnum": elfOversizedShnum(real),
144 "compressed data section": elfMutate(real, func(c []byte, shoff, _ uint64) {
145 h := c[shoff+3*64:]
146 le.PutUint64(h[8:], le.Uint64(h[8:])|0x800)
147 }),
148 "extended numbering": elfMutate(real, func(c []byte, _, _ uint64) { le.PutUint16(c[60:], 0) }),
149 "shstrndx extended": elfMutate(real, func(c []byte, _, _ uint64) { le.PutUint16(c[62:], 0xffff) }),
150 "shstrndx past table": elfMutate(real, func(c []byte, _, _ uint64) { le.PutUint16(c[62:], le.Uint16(c[60:])) }),
151 "oversized phnum": elfMutate(real, func(c []byte, _, _ uint64) { le.PutUint16(c[56:], 65000) }),
152 "section table outside": elfMutate(real, func(c []byte, _, _ uint64) { le.PutUint64(c[40:], uint64(len(c))) }),
153 "section table wraps": elfMutate(real, func(c []byte, _, _ uint64) { le.PutUint64(c[40:], ^uint64(0)-10) }),
154 "tables overlap": elfMutate(real, func(c []byte, _, phoff uint64) { le.PutUint64(c[40:], phoff) }),
155 "table over the header": elfMutate(real, func(c []byte, _, _ uint64) { le.PutUint64(c[32:], 0) }),
156 "wrong header entry size": elfMutate(real, func(c []byte, _, _ uint64) { le.PutUint16(c[58:], 40) }),
157 "section past the end": elfMutate(real, func(c []byte, shoff, _ uint64) {
158 h := c[shoff+1*64:]
159 le.PutUint32(h[4:], 1)
160 le.PutUint64(h[32:], uint64(len(c))+1)
161 }),
162 "segment past the end": elfMutate(real, func(c []byte, _, phoff uint64) { le.PutUint64(c[phoff+32:], uint64(len(c))+1) }),
163 "overlapping sections": elfMutate(real, func(c []byte, shoff, _ uint64) {
164 a, b := c[shoff+1*64:], c[shoff+2*64:]
165 le.PutUint32(a[4:], 1)
166 le.PutUint32(b[4:], 1)
167 le.PutUint64(b[24:], le.Uint64(a[24:]))
168 if le.Uint64(a[32:]) == 0 || le.Uint64(b[32:]) == 0 {
169 le.PutUint64(a[32:], 16)
170 le.PutUint64(b[32:], 16)
171 }
172 }),
173 "repeated 32 MiB name": elfRepeatedName(real, 32<<20),
174 "name table over 64 KiB": elfRepeatedName(real, 64<<10),
175 "repeated 257-byte name": elfRepeatedName(real, 257),
176 "name table not a strtab": elfMutate(real, func(c []byte, shoff, _ uint64) {
177 le.PutUint32(c[shoff+uint64(le.Uint16(c[62:]))*64+4:], 1)
178 }),
179 "empty name table": elfMutate(real, func(c []byte, shoff, _ uint64) {
180 le.PutUint64(c[shoff+uint64(le.Uint16(c[62:]))*64+32:], 0)
181 }),
182 "name offset outside the table": elfMutate(real, func(c []byte, shoff, _ uint64) {
183 size := le.Uint64(c[shoff+uint64(le.Uint16(c[62:]))*64+32:])
184 le.PutUint32(c[shoff+64:], uint32(size))
185 }),
186 "x86-64": elfMutate(real, func(c []byte, _, _ uint64) { le.PutUint16(c[18:], 62) }),
187 "32-bit class": elfMutate(real, func(c []byte, _, _ uint64) { c[4] = 1 }),
188 "big endian": elfMutate(real, func(c []byte, _, _ uint64) { c[5] = 2 }),
189 "not ELF": append([]byte("MZ"), real[2:]...),
190 "truncated": real[:63],
191 "empty": nil,
192 } {
193 if err := CheckELFBounds(crafted); !errors.Is(err, ErrELFBounds) {
194 t.Errorf("%s: %v", name, err)
195 }
196 if ArtifactBuild(crafted) != "" {
197 t.Errorf("%s: stamp read", name)
198 }
199 }
200}
201