File: workload.go

package info (click to toggle)
golang-github-containers-buildah 1.39.3%2Bds1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 7,724 kB
  • sloc: sh: 2,398; makefile: 236; perl: 187; asm: 16; awk: 12; ansic: 1
file content (223 lines) | stat: -rw-r--r-- 8,500 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
package mkcw

import (
	"bytes"
	"encoding/binary"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"os"

	"github.com/containers/buildah/define"
	"github.com/containers/buildah/internal/mkcw/types"
)

type (
	// WorkloadConfig is the data type which is encoded and stored in an image.
	WorkloadConfig = types.WorkloadConfig
	// SevWorkloadData is the type of data in WorkloadConfig.TeeData when the type is SEV.
	SevWorkloadData = types.SevWorkloadData
	// SnpWorkloadData is the type of data in WorkloadConfig.TeeData when the type is SNP.
	SnpWorkloadData = types.SnpWorkloadData
	// TeeType is one of the known types of trusted execution environments for which we
	// can generate suitable image contents.
	TeeType = define.TeeType
)

const (
	maxWorkloadConfigSize    = 1024 * 1024
	preferredPaddingBoundary = 4096
	// SEV is a known trusted execution environment type: AMD-SEV
	SEV = define.SEV
	// SEV_NO_ES is a known trusted execution environment type: AMD-SEV without encrypted state
	SEV_NO_ES = types.SEV_NO_ES //revive:disable-line:var-naming
	// SNP is a known trusted execution environment type: AMD-SNP
	SNP = define.SNP
	// krun looks for its configuration JSON directly in a disk image if the last twelve bytes
	// of the disk image are this magic value followed by a little-endian 64-bit
	// length-of-the-configuration
	krunMagic = "KRUN"
)

// ReadWorkloadConfigFromImage reads the workload configuration from the
// specified disk image file
func ReadWorkloadConfigFromImage(path string) (WorkloadConfig, error) {
	// Read the last 12 bytes, which should be "KRUN" followed by a 64-bit
	// little-endian length.  The (length) bytes immediately preceding
	// these hold the JSON-encoded workloadConfig.
	var wc WorkloadConfig
	f, err := os.Open(path)
	if err != nil {
		return wc, err
	}
	defer f.Close()

	// Read those last 12 bytes.
	finalTwelve := make([]byte, 12)
	if _, err = f.Seek(-12, io.SeekEnd); err != nil {
		return wc, fmt.Errorf("checking for workload config signature: %w", err)
	}
	if n, err := f.Read(finalTwelve); err != nil || n != len(finalTwelve) {
		if err != nil && !errors.Is(err, io.EOF) {
			return wc, fmt.Errorf("reading workload config signature (%d bytes read): %w", n, err)
		}
		if n != len(finalTwelve) {
			return wc, fmt.Errorf("short read (expected 12 bytes at the end of %q, got %d)", path, n)
		}
	}
	if magic := string(finalTwelve[0:4]); magic != "KRUN" {
		return wc, fmt.Errorf("expected magic string KRUN in %q, found %q)", path, magic)
	}
	length := binary.LittleEndian.Uint64(finalTwelve[4:])
	if length > maxWorkloadConfigSize {
		return wc, fmt.Errorf("workload config in %q is %d bytes long, which seems unreasonable (max allowed %d)", path, length, maxWorkloadConfigSize)
	}

	// Read and decode the config.
	configBytes := make([]byte, length)
	if _, err = f.Seek(-(int64(length) + 12), io.SeekEnd); err != nil {
		return wc, fmt.Errorf("looking for workload config from disk image: %w", err)
	}
	if n, err := f.Read(configBytes); err != nil || n != len(configBytes) {
		if err != nil {
			return wc, fmt.Errorf("reading workload config from disk image: %w", err)
		}
		return wc, fmt.Errorf("short read (expected %d bytes near the end of %q, got %d)", len(configBytes), path, n)
	}
	err = json.Unmarshal(configBytes, &wc)
	if err != nil {
		err = fmt.Errorf("unmarshaling configuration %q: %w", string(configBytes), err)
	}
	return wc, err
}

// WriteWorkloadConfigToImage writes the workload configuration to the
// specified disk image file, overwriting a previous configuration if it's
// asked to and it finds one
func WriteWorkloadConfigToImage(imageFile *os.File, workloadConfigBytes []byte, overwrite bool) error {
	// Read those last 12 bytes to check if there's a configuration there already, which we should overwrite.
	var overwriteOffset int64
	if overwrite {
		finalTwelve := make([]byte, 12)
		if _, err := imageFile.Seek(-12, io.SeekEnd); err != nil {
			return fmt.Errorf("checking for workload config signature: %w", err)
		}
		if n, err := imageFile.Read(finalTwelve); err != nil || n != len(finalTwelve) {
			if err != nil && !errors.Is(err, io.EOF) {
				return fmt.Errorf("reading workload config signature (%d bytes read): %w", n, err)
			}
			if n != len(finalTwelve) {
				return fmt.Errorf("short read (expected 12 bytes at the end of %q, got %d)", imageFile.Name(), n)
			}
		}
		if magic := string(finalTwelve[0:4]); magic == "KRUN" {
			length := binary.LittleEndian.Uint64(finalTwelve[4:])
			if length < maxWorkloadConfigSize {
				overwriteOffset = int64(length + 12)
			}
		}
	}
	// If we found a configuration in the file, try to figure out how much padding was used.
	paddingSize := int64(preferredPaddingBoundary)
	if overwriteOffset != 0 {
		st, err := imageFile.Stat()
		if err != nil {
			return err
		}
		for _, possiblePaddingLength := range []int64{0x100000, 0x10000, 0x1000, 0x200, 0x100} {
			if overwriteOffset > possiblePaddingLength {
				continue
			}
			if st.Size()%possiblePaddingLength != 0 {
				continue
			}
			if _, err := imageFile.Seek(-possiblePaddingLength, io.SeekEnd); err != nil {
				return fmt.Errorf("checking size of padding at end of file: %w", err)
			}
			buf := make([]byte, possiblePaddingLength)
			n, err := imageFile.Read(buf)
			if err != nil {
				return fmt.Errorf("reading possible padding at end of file: %w", err)
			}
			if n != len(buf) {
				return fmt.Errorf("short read checking size of padding at end of file: %d != %d", n, len(buf))
			}
			if bytes.Equal(buf[:possiblePaddingLength-overwriteOffset], make([]byte, possiblePaddingLength-overwriteOffset)) {
				// everything up to the configuration was zero bytes, so it was padding
				overwriteOffset = possiblePaddingLength
				paddingSize = possiblePaddingLength
				break
			}
		}
	}

	// Append the krun configuration to a new buffer.
	var formatted bytes.Buffer
	nWritten, err := formatted.Write(workloadConfigBytes)
	if err != nil {
		return fmt.Errorf("building workload config: %w", err)
	}
	if nWritten != len(workloadConfigBytes) {
		return fmt.Errorf("short write appending configuration to buffer: %d != %d", nWritten, len(workloadConfigBytes))
	}
	// Append the magic string to the buffer.
	nWritten, err = formatted.WriteString(krunMagic)
	if err != nil {
		return fmt.Errorf("building workload config signature: %w", err)
	}
	if nWritten != len(krunMagic) {
		return fmt.Errorf("short write appending krun magic to buffer: %d != %d", nWritten, len(krunMagic))
	}
	// Append the 64-bit little-endian length of the workload configuration to the buffer.
	workloadConfigLengthBytes := make([]byte, 8)
	binary.LittleEndian.PutUint64(workloadConfigLengthBytes, uint64(len(workloadConfigBytes)))
	nWritten, err = formatted.Write(workloadConfigLengthBytes)
	if err != nil {
		return fmt.Errorf("building workload config signature size: %w", err)
	}
	if nWritten != len(workloadConfigLengthBytes) {
		return fmt.Errorf("short write appending configuration length to buffer: %d != %d", nWritten, len(workloadConfigLengthBytes))
	}

	// Build a copy of that data, with padding preceding it.
	var padded bytes.Buffer
	if int64(formatted.Len())%paddingSize != 0 {
		extra := paddingSize - (int64(formatted.Len()) % paddingSize)
		nWritten, err := padded.Write(make([]byte, extra))
		if err != nil {
			return fmt.Errorf("buffering padding: %w", err)
		}
		if int64(nWritten) != extra {
			return fmt.Errorf("short write buffering padding for disk image: %d != %d", nWritten, extra)
		}
	}
	extra := int64(formatted.Len())
	nWritten, err = padded.Write(formatted.Bytes())
	if err != nil {
		return fmt.Errorf("buffering workload config: %w", err)
	}
	if int64(nWritten) != extra {
		return fmt.Errorf("short write buffering workload config: %d != %d", nWritten, extra)
	}

	// Write the buffer to the file, starting with padding.
	if _, err = imageFile.Seek(-overwriteOffset, io.SeekEnd); err != nil {
		return fmt.Errorf("preparing to write workload config: %w", err)
	}
	nWritten, err = imageFile.Write(padded.Bytes())
	if err != nil {
		return fmt.Errorf("writing workload config: %w", err)
	}
	if nWritten != padded.Len() {
		return fmt.Errorf("short write writing configuration to disk image: %d != %d", nWritten, padded.Len())
	}
	offset, err := imageFile.Seek(0, io.SeekCurrent)
	if err != nil {
		return fmt.Errorf("preparing mark end of disk image: %w", err)
	}
	if err = imageFile.Truncate(offset); err != nil {
		return fmt.Errorf("marking end of disk image: %w", err)
	}
	return nil
}