diff --git a/.changelog/5976.trivial.md b/.changelog/5976.trivial.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/go/oasis-test-runner/oasis/runtime.go b/go/oasis-test-runner/oasis/runtime.go index 4230475701b..eac620094b5 100644 --- a/go/oasis-test-runner/oasis/runtime.go +++ b/go/oasis-test-runner/oasis/runtime.go @@ -280,9 +280,11 @@ func (rt *Runtime) toRuntimeBundle(deploymentIndex int) (*bundle.Bundle, error) _ = bnd.Add(elfBin, bundle.NewBytesData(binBuf)) comp := &bundle.Component{ - Kind: compCfg.Kind, - Version: compCfg.Version, - Executable: elfBin, + Kind: compCfg.Kind, + Version: compCfg.Version, + ELF: &bundle.ELFMetadata{ + Executable: elfBin, + }, } if rt.teeHardware == node.TEEHardwareIntelSGX { diff --git a/go/runtime/bundle/bundle.go b/go/runtime/bundle/bundle.go index 4505b0b9ffa..0dffb4c176e 100644 --- a/go/runtime/bundle/bundle.go +++ b/go/runtime/bundle/bundle.go @@ -50,11 +50,22 @@ func (bnd *Bundle) Validate() error { } var needFiles []bundleFile for id, comp := range bnd.Manifest.GetAvailableComponents() { - needFiles = append(needFiles, bundleFile{ - descr: fmt.Sprintf("%s: ELF executable", id), - fn: comp.Executable, - optional: true, - }) + if elf := comp.ELF; elf != nil { + needFiles = append(needFiles, + []bundleFile{ + { + descr: fmt.Sprintf("%s: ELF executable", id), + fn: elf.Executable, + }, + }..., + ) + } else { + needFiles = append(needFiles, bundleFile{ + descr: fmt.Sprintf("%s: ELF executable", id), + fn: comp.Executable, + optional: true, + }) + } if sgx := comp.SGX; sgx != nil { needFiles = append(needFiles, []bundleFile{ @@ -494,7 +505,11 @@ func (bnd *Bundle) WriteExploded(dataDir string) (string, error) { } for id, comp := range bnd.Manifest.GetAvailableComponents() { - if comp.Executable != "" { + if comp.ELF != nil { + if err := os.Chmod(bnd.ExplodedPath(dataDir, comp.ELF.Executable), 0o700); err != nil { + return "", fmt.Errorf("runtime/bundle: failed to fixup executable permissions for '%s': %w", id, err) + } + } else if comp.Executable != "" { if err := os.Chmod(bnd.ExplodedPath(dataDir, comp.Executable), 0o700); err != nil { return "", fmt.Errorf("runtime/bundle: failed to fixup executable permissions for '%s': %w", id, err) } diff --git a/go/runtime/bundle/bundle_test.go b/go/runtime/bundle/bundle_test.go index e48b252ee34..0d07b626156 100644 --- a/go/runtime/bundle/bundle_test.go +++ b/go/runtime/bundle/bundle_test.go @@ -30,8 +30,10 @@ func TestBundle(t *testing.T) { }(), Components: []*Component{ { - Kind: component.RONL, - Executable: "runtime.bin", + Kind: component.RONL, + ELF: &ELFMetadata{ + Executable: "runtime.bin", + }, SGX: &SGXMetadata{ Executable: "runtime.sgx", }, @@ -54,7 +56,7 @@ func TestBundle(t *testing.T) { require.False(t, manifest.IsDetached(), "manifest with RONL component should not be detached") t.Run("Add_Write", func(t *testing.T) { - err := bundle.Add(manifest.Components[0].Executable, NewBytesData(randBuffer(3252))) + err := bundle.Add(manifest.Components[0].ELF.Executable, NewBytesData(randBuffer(3252))) require.NoError(t, err, "bundle.Add(elf)") err = bundle.Add(manifest.Components[0].SGX.Executable, NewBytesData(randBuffer(6541))) require.NoError(t, err, "bundle.Add(sgx)") @@ -84,7 +86,7 @@ func TestBundle(t *testing.T) { t.Run("Open_WithManifestHash", func(t *testing.T) { var manifestHash hash.Hash - err := manifestHash.UnmarshalHex("905e9866eccb967e8991698273f41d20c616cab4f9e9332a2a12d9d3a1c8a486") + err := manifestHash.UnmarshalHex("2faad6101e24f0034a82b99aee10f4de909a00b1b2c125f7d6fb97d65dc7984b") require.NoError(t, err, "UnmarshalHex") _, err = Open(bundleFn, WithManifestHash(manifestHash)) @@ -92,7 +94,7 @@ func TestBundle(t *testing.T) { _, err = Open(bundleFn, WithManifestHash(hash.Hash{})) require.Error(t, err, "Open_WithManifestHash") - require.ErrorContains(t, err, "invalid manifest (got: 905e9866eccb967e8991698273f41d20c616cab4f9e9332a2a12d9d3a1c8a486, expected: 0000000000000000000000000000000000000000000000000000000000000000)") + require.ErrorContains(t, err, "invalid manifest (got: 2faad6101e24f0034a82b99aee10f4de909a00b1b2c125f7d6fb97d65dc7984b, expected: 0000000000000000000000000000000000000000000000000000000000000000)") }) t.Run("ResetManifest", func(t *testing.T) { @@ -137,8 +139,10 @@ func TestDetachedBundle(t *testing.T) { Components: []*Component{ // No RONL component in the manifest. { - Kind: component.ROFL, - Executable: "runtime.bin", + Kind: component.ROFL, + ELF: &ELFMetadata{ + Executable: "runtime.bin", + }, SGX: &SGXMetadata{ Executable: "runtime.sgx", }, @@ -152,7 +156,7 @@ func TestDetachedBundle(t *testing.T) { require.True(t, manifest.IsDetached(), "manifest without RONL component should be detached") t.Run("Add_Write", func(t *testing.T) { - err := bundle.Add(manifest.Components[0].Executable, NewBytesData(randBuffer(2231))) + err := bundle.Add(manifest.Components[0].ELF.Executable, NewBytesData(randBuffer(2231))) require.NoError(t, err, "bundle.Add(elf)") err = bundle.Add(manifest.Components[0].SGX.Executable, NewBytesData(randBuffer(7627))) require.NoError(t, err, "bundle.Add(sgx)") diff --git a/go/runtime/bundle/component.go b/go/runtime/bundle/component.go index b821bc3066a..4da32574538 100644 --- a/go/runtime/bundle/component.go +++ b/go/runtime/bundle/component.go @@ -5,6 +5,7 @@ import ( "path/filepath" "github.com/oasisprotocol/oasis-core/go/common" + "github.com/oasisprotocol/oasis-core/go/common/sgx" "github.com/oasisprotocol/oasis-core/go/common/version" "github.com/oasisprotocol/oasis-core/go/runtime/bundle/component" ) @@ -40,8 +41,12 @@ type Component struct { Version version.Version // Executable is the name of the runtime ELF executable file if any. + // NOTE: This may go away in the future, use `ELF` instead. Executable string `json:"executable,omitempty"` + // ELF is the ELF specific manifest metadata if any. + ELF *ELFMetadata `json:"elf,omitempty"` + // SGX is the SGX specific manifest metadata if any. SGX *SGXMetadata `json:"sgx,omitempty"` @@ -78,6 +83,12 @@ func (c *Component) Validate() error { ) { return fmt.Errorf("each component can only include metadata for a single TEE") } + if c.ELF != nil { + err := c.ELF.Validate() + if err != nil { + return fmt.Errorf("elf: %w", err) + } + } if c.SGX != nil { err := c.SGX.Validate() if err != nil { @@ -96,7 +107,7 @@ func (c *Component) Validate() error { if c.Name != "" { return fmt.Errorf("RONL component must have an empty name") } - if c.Executable == "" { + if c.Executable == "" && c.ELF == nil { return fmt.Errorf("RONL component must define an executable") } if c.Disabled { @@ -123,7 +134,7 @@ func (c *Component) IsNetworkAllowed() bool { // IsTEERequired returns true iff the component only provides TEE executables. func (c *Component) IsTEERequired() bool { - return c.Executable == "" && c.TEEKind() != component.TEEKindNone + return c.Executable == "" && c.ELF == nil && c.TEEKind() != component.TEEKindNone } // TEEKind returns the kind of TEE supported by the component. @@ -137,3 +148,120 @@ func (c *Component) TEEKind() component.TEEKind { return component.TEEKindNone } } + +// ELFMetadata is the ELF specific manifest metadata. +type ELFMetadata struct { + // Executable is the name of the ELF executable file. + Executable string `json:"executable"` +} + +// Validate validates the ELF metadata structure for well-formedness. +func (e *ELFMetadata) Validate() error { + if e.Executable == "" { + return fmt.Errorf("executable must be set") + } + return nil +} + +// SGXMetadata is the SGX specific manifest metadata. +type SGXMetadata struct { + // Executable is the name of the SGX enclave executable file. + Executable string `json:"executable"` + + // Signature is the name of the SGX enclave signature file. + Signature string `json:"signature"` +} + +// Validate validates the SGX metadata structure for well-formedness. +func (s *SGXMetadata) Validate() error { + if s.Executable == "" { + return fmt.Errorf("executable must be set") + } + return nil +} + +// TDXMetadata is the TDX specific manifest metadata. +// +// Note that changes to these fields may change the TD measurements. +type TDXMetadata struct { + // Firmware is the name of the virtual firmware file. It should rarely change and multiple + // components may use the same firmware. + Firmware string `json:"firmware"` + // Kernel is the name of the kernel image file. It should rarely change and multiple components + // may use the same kernel. + Kernel string `json:"kernel,omitempty"` + // InitRD is the name of the initial RAM disk image file. It should rarely change and multiple + // components may use the same initrd. + InitRD string `json:"initrd,omitempty"` + // ExtraKernelOptions are the extra kernel options to pass to the kernel after any of the + // default options. Note that kernel options affect TD measurements. + ExtraKernelOptions []string `json:"extra_kernel_options,omitempty"` + + // Stage2Image is the name of the stage 2 VM image file. + Stage2Image string `json:"stage2_image,omitempty"` + + // Resources are the requested VM resources. + Resources TDXResources `json:"resources"` +} + +// Validate validates the TDX metadata structure for well-formedness. +func (t *TDXMetadata) Validate() error { + if t.Firmware == "" { + return fmt.Errorf("firmware must be set") + } + if !t.HasKernel() && t.HasStage2() { + return fmt.Errorf("kernel must be set if stage 2 image is set") + } + if !t.HasKernel() && t.HasInitRD() { + return fmt.Errorf("kernel must be set if initrd image is set") + } + if err := t.Resources.Validate(); err != nil { + return err + } + return nil +} + +// HasKernel returns true iff the TDX metadata indicates there is a kernel present. +func (t *TDXMetadata) HasKernel() bool { + return t.Kernel != "" +} + +// HasInitRD returns true iff the TDX metadata indicates there is an initial RAM disk image present. +func (t *TDXMetadata) HasInitRD() bool { + return t.InitRD != "" +} + +// HasStage2 returns true iff the TDX metadata indicates there is a stage 2 image present. +func (t *TDXMetadata) HasStage2() bool { + return t.Stage2Image != "" +} + +// TDXResources are the requested VM resources for TDX VMs. +// +// Note that changes to these fields may change the TD measurements. +type TDXResources struct { + // Memory is the requested VM memory amount in megabytes. + Memory uint64 `json:"memory"` + // CPUCount is the requested number of vCPUs. + CPUCount uint8 `json:"cpus"` +} + +// Validate validates the VM resources. +func (r *TDXResources) Validate() error { + if r.Memory < 16 { + return fmt.Errorf("memory limit must be at least 16M") + } + if r.CPUCount < 1 { + return fmt.Errorf("vCPU count must be at least 1") + } + return nil +} + +// Identity is the cryptographic identity of a component. +type Identity struct { + // Hypervisor is the optional hypervisor this identity is for. + Hypervisor string `json:"hypervisor,omitempty"` + + // Enclave is the enclave identity. + Enclave sgx.EnclaveIdentity `json:"enclave"` +} diff --git a/go/runtime/bundle/manifest.go b/go/runtime/bundle/manifest.go index 7a1c656c322..5f90c737978 100644 --- a/go/runtime/bundle/manifest.go +++ b/go/runtime/bundle/manifest.go @@ -5,7 +5,6 @@ import ( "github.com/oasisprotocol/oasis-core/go/common" "github.com/oasisprotocol/oasis-core/go/common/crypto/hash" - "github.com/oasisprotocol/oasis-core/go/common/sgx" "github.com/oasisprotocol/oasis-core/go/common/version" "github.com/oasisprotocol/oasis-core/go/runtime/bundle/component" ) @@ -114,10 +113,12 @@ func (m *Manifest) GetComponentByID(id component.ID) *Component { // We also support legacy manifests which define the RONL component at the top-level. if id.IsRONL() && m.IsLegacy() { return &Component{ - Kind: component.RONL, - Executable: m.Executable, - Version: m.Version, - SGX: m.SGX, + Kind: component.RONL, + Version: m.Version, + ELF: &ELFMetadata{ + Executable: m.Executable, + }, + SGX: m.SGX, } } return nil @@ -140,106 +141,3 @@ func (m *Manifest) GetVersion() version.Version { return m.Version } - -// SGXMetadata is the SGX specific manifest metadata. -type SGXMetadata struct { - // Executable is the name of the SGX enclave executable file. - Executable string `json:"executable"` - - // Signature is the name of the SGX enclave signature file. - Signature string `json:"signature"` -} - -// Validate validates the SGX metadata structure for well-formedness. -func (s *SGXMetadata) Validate() error { - if s.Executable == "" { - return fmt.Errorf("executable must be set") - } - return nil -} - -// TDXMetadata is the TDX specific manifest metadata. -// -// Note that changes to these fields may change the TD measurements. -type TDXMetadata struct { - // Firmware is the name of the virtual firmware file. It should rarely change and multiple - // components may use the same firmware. - Firmware string `json:"firmware"` - // Kernel is the name of the kernel image file. It should rarely change and multiple components - // may use the same kernel. - Kernel string `json:"kernel,omitempty"` - // InitRD is the name of the initial RAM disk image file. It should rarely change and multiple - // components may use the same initrd. - InitRD string `json:"initrd,omitempty"` - // ExtraKernelOptions are the extra kernel options to pass to the kernel after any of the - // default options. Note that kernel options affect TD measurements. - ExtraKernelOptions []string `json:"extra_kernel_options,omitempty"` - - // Stage2Image is the name of the stage 2 VM image file. - Stage2Image string `json:"stage2_image,omitempty"` - - // Resources are the requested VM resources. - Resources TDXResources `json:"resources"` -} - -// Validate validates the TDX metadata structure for well-formedness. -func (t *TDXMetadata) Validate() error { - if t.Firmware == "" { - return fmt.Errorf("firmware must be set") - } - if !t.HasKernel() && t.HasStage2() { - return fmt.Errorf("kernel must be set if stage 2 image is set") - } - if !t.HasKernel() && t.HasInitRD() { - return fmt.Errorf("kernel must be set if initrd image is set") - } - if err := t.Resources.Validate(); err != nil { - return err - } - return nil -} - -// HasKernel returns true iff the TDX metadata indicates there is a kernel present. -func (t *TDXMetadata) HasKernel() bool { - return t.Kernel != "" -} - -// HasInitRD returns true iff the TDX metadata indicates there is an initial RAM disk image present. -func (t *TDXMetadata) HasInitRD() bool { - return t.InitRD != "" -} - -// HasStage2 returns true iff the TDX metadata indicates there is a stage 2 image present. -func (t *TDXMetadata) HasStage2() bool { - return t.Stage2Image != "" -} - -// TDXResources are the requested VM resources for TDX VMs. -// -// Note that changes to these fields may change the TD measurements. -type TDXResources struct { - // Memory is the requested VM memory amount in megabytes. - Memory uint64 `json:"memory"` - // CPUCount is the requested number of vCPUs. - CPUCount uint8 `json:"cpus"` -} - -// Validate validates the VM resources. -func (r *TDXResources) Validate() error { - if r.Memory < 16 { - return fmt.Errorf("memory limit must be at least 16M") - } - if r.CPUCount < 1 { - return fmt.Errorf("vCPU count must be at least 1") - } - return nil -} - -// Identity is the cryptographic identity of a component. -type Identity struct { - // Hypervisor is the optional hypervisor this identity is for. - Hypervisor string `json:"hypervisor,omitempty"` - - // Enclave is the enclave identity. - Enclave sgx.EnclaveIdentity `json:"enclave"` -} diff --git a/go/runtime/bundle/registry.go b/go/runtime/bundle/registry.go index c47a1aa8c3d..18b581f976a 100644 --- a/go/runtime/bundle/registry.go +++ b/go/runtime/bundle/registry.go @@ -264,8 +264,10 @@ func (r *registry) GetComponents(runtimeID common.Namespace, version version.Ver return []*ExplodedComponent{ { Component: &Component{ - Kind: component.RONL, - Executable: "mock", + Kind: component.RONL, + ELF: &ELFMetadata{ + Executable: "mock", + }, }, Detached: false, }, diff --git a/go/runtime/bundle/registry_test.go b/go/runtime/bundle/registry_test.go index 503a1fe8bdd..215ef2ef531 100644 --- a/go/runtime/bundle/registry_test.go +++ b/go/runtime/bundle/registry_test.go @@ -25,9 +25,11 @@ func createSyntheticBundle(dir string, runtimeID common.Namespace, version versi switch comp { case component.RONL: manifest.Components = append(manifest.Components, &Component{ - Kind: component.RONL, - Version: version, - Executable: "runtime.bin", + Kind: component.RONL, + Version: version, + ELF: &ELFMetadata{ + Executable: "runtime.bin", + }, }) case component.ROFL: manifest.Components = append(manifest.Components, &Component{ @@ -43,7 +45,7 @@ func createSyntheticBundle(dir string, runtimeID common.Namespace, version versi } if slices.Contains(components, component.RONL) { - if err := bnd.Add(manifest.Components[0].Executable, NewBytesData([]byte{1})); err != nil { + if err := bnd.Add(manifest.Components[0].ELF.Executable, NewBytesData([]byte{1})); err != nil { return "", err } } diff --git a/go/runtime/host/sandbox/sandbox.go b/go/runtime/host/sandbox/sandbox.go index 3893895c31d..dc70786d4ef 100644 --- a/go/runtime/host/sandbox/sandbox.go +++ b/go/runtime/host/sandbox/sandbox.go @@ -616,8 +616,13 @@ func DefaultGetSandboxConfig(logger *logging.Logger, sandboxBinaryPath string) G "provisioner", "sandbox", ) + executable := comp.Executable + if comp.ELF != nil { + executable = comp.ELF.Executable + } + return process.Config{ - Path: comp.ExplodedPath(comp.Executable), + Path: comp.ExplodedPath(executable), SandboxBinaryPath: sandboxBinaryPath, Stdout: logWrapper, Stderr: logWrapper,