Skip to content

Commit fed855c

Browse files
authored
Merge pull request #5977 from oasisprotocol/peternose/trivial/elf-metadata
go/runtime/bundle/component: Add ELF metadata
2 parents 76552be + c483e20 commit fed855c

File tree

9 files changed

+190
-134
lines changed

9 files changed

+190
-134
lines changed

.changelog/5976.trivial.md

Whitespace-only changes.

go/oasis-test-runner/oasis/runtime.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -280,9 +280,11 @@ func (rt *Runtime) toRuntimeBundle(deploymentIndex int) (*bundle.Bundle, error)
280280
_ = bnd.Add(elfBin, bundle.NewBytesData(binBuf))
281281

282282
comp := &bundle.Component{
283-
Kind: compCfg.Kind,
284-
Version: compCfg.Version,
285-
Executable: elfBin,
283+
Kind: compCfg.Kind,
284+
Version: compCfg.Version,
285+
ELF: &bundle.ELFMetadata{
286+
Executable: elfBin,
287+
},
286288
}
287289

288290
if rt.teeHardware == node.TEEHardwareIntelSGX {

go/runtime/bundle/bundle.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,22 @@ func (bnd *Bundle) Validate() error {
5050
}
5151
var needFiles []bundleFile
5252
for id, comp := range bnd.Manifest.GetAvailableComponents() {
53-
needFiles = append(needFiles, bundleFile{
54-
descr: fmt.Sprintf("%s: ELF executable", id),
55-
fn: comp.Executable,
56-
optional: true,
57-
})
53+
if elf := comp.ELF; elf != nil {
54+
needFiles = append(needFiles,
55+
[]bundleFile{
56+
{
57+
descr: fmt.Sprintf("%s: ELF executable", id),
58+
fn: elf.Executable,
59+
},
60+
}...,
61+
)
62+
} else {
63+
needFiles = append(needFiles, bundleFile{
64+
descr: fmt.Sprintf("%s: ELF executable", id),
65+
fn: comp.Executable,
66+
optional: true,
67+
})
68+
}
5869
if sgx := comp.SGX; sgx != nil {
5970
needFiles = append(needFiles,
6071
[]bundleFile{
@@ -494,7 +505,11 @@ func (bnd *Bundle) WriteExploded(dataDir string) (string, error) {
494505
}
495506

496507
for id, comp := range bnd.Manifest.GetAvailableComponents() {
497-
if comp.Executable != "" {
508+
if comp.ELF != nil {
509+
if err := os.Chmod(bnd.ExplodedPath(dataDir, comp.ELF.Executable), 0o700); err != nil {
510+
return "", fmt.Errorf("runtime/bundle: failed to fixup executable permissions for '%s': %w", id, err)
511+
}
512+
} else if comp.Executable != "" {
498513
if err := os.Chmod(bnd.ExplodedPath(dataDir, comp.Executable), 0o700); err != nil {
499514
return "", fmt.Errorf("runtime/bundle: failed to fixup executable permissions for '%s': %w", id, err)
500515
}

go/runtime/bundle/bundle_test.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ func TestBundle(t *testing.T) {
3030
}(),
3131
Components: []*Component{
3232
{
33-
Kind: component.RONL,
34-
Executable: "runtime.bin",
33+
Kind: component.RONL,
34+
ELF: &ELFMetadata{
35+
Executable: "runtime.bin",
36+
},
3537
SGX: &SGXMetadata{
3638
Executable: "runtime.sgx",
3739
},
@@ -54,7 +56,7 @@ func TestBundle(t *testing.T) {
5456
require.False(t, manifest.IsDetached(), "manifest with RONL component should not be detached")
5557

5658
t.Run("Add_Write", func(t *testing.T) {
57-
err := bundle.Add(manifest.Components[0].Executable, NewBytesData(randBuffer(3252)))
59+
err := bundle.Add(manifest.Components[0].ELF.Executable, NewBytesData(randBuffer(3252)))
5860
require.NoError(t, err, "bundle.Add(elf)")
5961
err = bundle.Add(manifest.Components[0].SGX.Executable, NewBytesData(randBuffer(6541)))
6062
require.NoError(t, err, "bundle.Add(sgx)")
@@ -84,15 +86,15 @@ func TestBundle(t *testing.T) {
8486

8587
t.Run("Open_WithManifestHash", func(t *testing.T) {
8688
var manifestHash hash.Hash
87-
err := manifestHash.UnmarshalHex("905e9866eccb967e8991698273f41d20c616cab4f9e9332a2a12d9d3a1c8a486")
89+
err := manifestHash.UnmarshalHex("2faad6101e24f0034a82b99aee10f4de909a00b1b2c125f7d6fb97d65dc7984b")
8890
require.NoError(t, err, "UnmarshalHex")
8991

9092
_, err = Open(bundleFn, WithManifestHash(manifestHash))
9193
require.NoError(t, err, "Open_WithManifestHash")
9294

9395
_, err = Open(bundleFn, WithManifestHash(hash.Hash{}))
9496
require.Error(t, err, "Open_WithManifestHash")
95-
require.ErrorContains(t, err, "invalid manifest (got: 905e9866eccb967e8991698273f41d20c616cab4f9e9332a2a12d9d3a1c8a486, expected: 0000000000000000000000000000000000000000000000000000000000000000)")
97+
require.ErrorContains(t, err, "invalid manifest (got: 2faad6101e24f0034a82b99aee10f4de909a00b1b2c125f7d6fb97d65dc7984b, expected: 0000000000000000000000000000000000000000000000000000000000000000)")
9698
})
9799

98100
t.Run("ResetManifest", func(t *testing.T) {
@@ -137,8 +139,10 @@ func TestDetachedBundle(t *testing.T) {
137139
Components: []*Component{
138140
// No RONL component in the manifest.
139141
{
140-
Kind: component.ROFL,
141-
Executable: "runtime.bin",
142+
Kind: component.ROFL,
143+
ELF: &ELFMetadata{
144+
Executable: "runtime.bin",
145+
},
142146
SGX: &SGXMetadata{
143147
Executable: "runtime.sgx",
144148
},
@@ -152,7 +156,7 @@ func TestDetachedBundle(t *testing.T) {
152156
require.True(t, manifest.IsDetached(), "manifest without RONL component should be detached")
153157

154158
t.Run("Add_Write", func(t *testing.T) {
155-
err := bundle.Add(manifest.Components[0].Executable, NewBytesData(randBuffer(2231)))
159+
err := bundle.Add(manifest.Components[0].ELF.Executable, NewBytesData(randBuffer(2231)))
156160
require.NoError(t, err, "bundle.Add(elf)")
157161
err = bundle.Add(manifest.Components[0].SGX.Executable, NewBytesData(randBuffer(7627)))
158162
require.NoError(t, err, "bundle.Add(sgx)")

go/runtime/bundle/component.go

Lines changed: 130 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"path/filepath"
66

77
"github.com/oasisprotocol/oasis-core/go/common"
8+
"github.com/oasisprotocol/oasis-core/go/common/sgx"
89
"github.com/oasisprotocol/oasis-core/go/common/version"
910
"github.com/oasisprotocol/oasis-core/go/runtime/bundle/component"
1011
)
@@ -40,8 +41,12 @@ type Component struct {
4041
Version version.Version
4142

4243
// Executable is the name of the runtime ELF executable file if any.
44+
// NOTE: This may go away in the future, use `ELF` instead.
4345
Executable string `json:"executable,omitempty"`
4446

47+
// ELF is the ELF specific manifest metadata if any.
48+
ELF *ELFMetadata `json:"elf,omitempty"`
49+
4550
// SGX is the SGX specific manifest metadata if any.
4651
SGX *SGXMetadata `json:"sgx,omitempty"`
4752

@@ -78,6 +83,12 @@ func (c *Component) Validate() error {
7883
) {
7984
return fmt.Errorf("each component can only include metadata for a single TEE")
8085
}
86+
if c.ELF != nil {
87+
err := c.ELF.Validate()
88+
if err != nil {
89+
return fmt.Errorf("elf: %w", err)
90+
}
91+
}
8192
if c.SGX != nil {
8293
err := c.SGX.Validate()
8394
if err != nil {
@@ -96,7 +107,7 @@ func (c *Component) Validate() error {
96107
if c.Name != "" {
97108
return fmt.Errorf("RONL component must have an empty name")
98109
}
99-
if c.Executable == "" {
110+
if c.Executable == "" && c.ELF == nil {
100111
return fmt.Errorf("RONL component must define an executable")
101112
}
102113
if c.Disabled {
@@ -123,7 +134,7 @@ func (c *Component) IsNetworkAllowed() bool {
123134

124135
// IsTEERequired returns true iff the component only provides TEE executables.
125136
func (c *Component) IsTEERequired() bool {
126-
return c.Executable == "" && c.TEEKind() != component.TEEKindNone
137+
return c.Executable == "" && c.ELF == nil && c.TEEKind() != component.TEEKindNone
127138
}
128139

129140
// TEEKind returns the kind of TEE supported by the component.
@@ -137,3 +148,120 @@ func (c *Component) TEEKind() component.TEEKind {
137148
return component.TEEKindNone
138149
}
139150
}
151+
152+
// ELFMetadata is the ELF specific manifest metadata.
153+
type ELFMetadata struct {
154+
// Executable is the name of the ELF executable file.
155+
Executable string `json:"executable"`
156+
}
157+
158+
// Validate validates the ELF metadata structure for well-formedness.
159+
func (e *ELFMetadata) Validate() error {
160+
if e.Executable == "" {
161+
return fmt.Errorf("executable must be set")
162+
}
163+
return nil
164+
}
165+
166+
// SGXMetadata is the SGX specific manifest metadata.
167+
type SGXMetadata struct {
168+
// Executable is the name of the SGX enclave executable file.
169+
Executable string `json:"executable"`
170+
171+
// Signature is the name of the SGX enclave signature file.
172+
Signature string `json:"signature"`
173+
}
174+
175+
// Validate validates the SGX metadata structure for well-formedness.
176+
func (s *SGXMetadata) Validate() error {
177+
if s.Executable == "" {
178+
return fmt.Errorf("executable must be set")
179+
}
180+
return nil
181+
}
182+
183+
// TDXMetadata is the TDX specific manifest metadata.
184+
//
185+
// Note that changes to these fields may change the TD measurements.
186+
type TDXMetadata struct {
187+
// Firmware is the name of the virtual firmware file. It should rarely change and multiple
188+
// components may use the same firmware.
189+
Firmware string `json:"firmware"`
190+
// Kernel is the name of the kernel image file. It should rarely change and multiple components
191+
// may use the same kernel.
192+
Kernel string `json:"kernel,omitempty"`
193+
// InitRD is the name of the initial RAM disk image file. It should rarely change and multiple
194+
// components may use the same initrd.
195+
InitRD string `json:"initrd,omitempty"`
196+
// ExtraKernelOptions are the extra kernel options to pass to the kernel after any of the
197+
// default options. Note that kernel options affect TD measurements.
198+
ExtraKernelOptions []string `json:"extra_kernel_options,omitempty"`
199+
200+
// Stage2Image is the name of the stage 2 VM image file.
201+
Stage2Image string `json:"stage2_image,omitempty"`
202+
203+
// Resources are the requested VM resources.
204+
Resources TDXResources `json:"resources"`
205+
}
206+
207+
// Validate validates the TDX metadata structure for well-formedness.
208+
func (t *TDXMetadata) Validate() error {
209+
if t.Firmware == "" {
210+
return fmt.Errorf("firmware must be set")
211+
}
212+
if !t.HasKernel() && t.HasStage2() {
213+
return fmt.Errorf("kernel must be set if stage 2 image is set")
214+
}
215+
if !t.HasKernel() && t.HasInitRD() {
216+
return fmt.Errorf("kernel must be set if initrd image is set")
217+
}
218+
if err := t.Resources.Validate(); err != nil {
219+
return err
220+
}
221+
return nil
222+
}
223+
224+
// HasKernel returns true iff the TDX metadata indicates there is a kernel present.
225+
func (t *TDXMetadata) HasKernel() bool {
226+
return t.Kernel != ""
227+
}
228+
229+
// HasInitRD returns true iff the TDX metadata indicates there is an initial RAM disk image present.
230+
func (t *TDXMetadata) HasInitRD() bool {
231+
return t.InitRD != ""
232+
}
233+
234+
// HasStage2 returns true iff the TDX metadata indicates there is a stage 2 image present.
235+
func (t *TDXMetadata) HasStage2() bool {
236+
return t.Stage2Image != ""
237+
}
238+
239+
// TDXResources are the requested VM resources for TDX VMs.
240+
//
241+
// Note that changes to these fields may change the TD measurements.
242+
type TDXResources struct {
243+
// Memory is the requested VM memory amount in megabytes.
244+
Memory uint64 `json:"memory"`
245+
// CPUCount is the requested number of vCPUs.
246+
CPUCount uint8 `json:"cpus"`
247+
}
248+
249+
// Validate validates the VM resources.
250+
func (r *TDXResources) Validate() error {
251+
if r.Memory < 16 {
252+
return fmt.Errorf("memory limit must be at least 16M")
253+
}
254+
if r.CPUCount < 1 {
255+
return fmt.Errorf("vCPU count must be at least 1")
256+
}
257+
return nil
258+
}
259+
260+
// Identity is the cryptographic identity of a component.
261+
type Identity struct {
262+
// Hypervisor is the optional hypervisor this identity is for.
263+
Hypervisor string `json:"hypervisor,omitempty"`
264+
265+
// Enclave is the enclave identity.
266+
Enclave sgx.EnclaveIdentity `json:"enclave"`
267+
}

0 commit comments

Comments
 (0)