diff --git a/.changelog/1707.feature.md b/.changelog/1707.feature.md new file mode 100644 index 00000000000..38d6e1c3338 --- /dev/null +++ b/.changelog/1707.feature.md @@ -0,0 +1,6 @@ +go/runtime/host/sgx: Add support for SIGSTRUCTs + +For now this will just generate one, signed with the same key that +`runtime-loader` used to use (the Fortanix dummy key), but this will +also support using file backed signatures, once we have an idea on how +we are going to handle the process for such things. diff --git a/go/common/sgx/sigstruct/debug_builder.go b/go/common/sgx/sigstruct/debug_builder.go new file mode 100644 index 00000000000..a9d1e0e5316 --- /dev/null +++ b/go/common/sgx/sigstruct/debug_builder.go @@ -0,0 +1,40 @@ +package sigstruct + +import ( + "fmt" + + "github.com/oasislabs/oasis-core/go/common/sgx" +) + +// UnsafeDebugForEnclave returns the SIGSTRUCT corresponding to the provided +// SGX enclave binary, signed using the Fortanix Rust SDK's dummy signing key. +// +// This routine is deterministic, and MUST only ever be used for testing. +func UnsafeDebugForEnclave(sgxs []byte) ([]byte, error) { + // Note: The key is unavailable unless DontBlameOasis is enabled. + signingKey := sgx.UnsafeFortanixDummyKey() + if signingKey == nil { + return nil, fmt.Errorf("sgx/sigstruct: debug signing key unavailable") + } + + var enclaveHash sgx.MrEnclave + if err := enclaveHash.FromSgxsBytes(sgxs); err != nil { + return nil, fmt.Errorf("sgx/sigstruct: failed to derive EnclaveHash: %w", err) + } + + builder := New( + WithAttributes(sgx.Attributes{ + Flags: sgx.AttributeDebug | sgx.AttributeMode64Bit, + Xfrm: 3, // X87, SSE ("XFRM[1:0] must be set to 0x3") + }), + WithAttributesMask([2]uint64{^uint64(0), ^uint64(0)}), + WithEnclaveHash(enclaveHash), + ) + + ret, err := builder.Sign(signingKey) + if err != nil { + return nil, fmt.Errorf("sgx/sigstruct: failed to sign with test key: %w", err) + } + + return ret, nil +} diff --git a/go/runtime/host/sandbox/sandbox.go b/go/runtime/host/sandbox/sandbox.go index 76108cf15ca..30b400bdbd3 100644 --- a/go/runtime/host/sandbox/sandbox.go +++ b/go/runtime/host/sandbox/sandbox.go @@ -37,7 +37,7 @@ const ( type Config struct { // GetSandboxConfig is a function that generates the sandbox configuration. In case it is not // specified a default function is used. - GetSandboxConfig func(cfg host.Config, socketPath string, runtimeDir string) process.Config + GetSandboxConfig func(cfg host.Config, socketPath string, runtimeDir string) (process.Config, error) // HostInitializer is a function that additionally initializes the runtime host. In case it is // not specified a default function is used. @@ -198,7 +198,10 @@ func (r *sandboxedRuntime) startProcess() (err error) { // No sandbox. r.logger.Warn("starting an UNSANDBOXED runtime") - cfg := r.cfg.GetSandboxConfig(r.rtCfg, hostSocket, runtimeDir) + cfg, cErr := r.cfg.GetSandboxConfig(r.rtCfg, hostSocket, runtimeDir) + if cErr != nil { + return fmt.Errorf("failed to configure process: %w", cErr) + } p, err = process.NewNaked(cfg) if err != nil { @@ -206,7 +209,10 @@ func (r *sandboxedRuntime) startProcess() (err error) { } case false: // With sandbox. - cfg := r.cfg.GetSandboxConfig(r.rtCfg, bindHostSocketPath, runtimeDir) + cfg, cErr := r.cfg.GetSandboxConfig(r.rtCfg, bindHostSocketPath, runtimeDir) + if cErr != nil { + return fmt.Errorf("failed to configure sandbox: %w", cErr) + } if cfg.BindRW == nil { cfg.BindRW = make(map[string]string) @@ -475,13 +481,13 @@ func (r *sandboxedRuntime) manager() { func New(cfg Config) (host.Provisioner, error) { // Use a default GetSandboxConfig if none was provided. if cfg.GetSandboxConfig == nil { - cfg.GetSandboxConfig = func(cfg host.Config, socketPath string, runtimeDir string) process.Config { + cfg.GetSandboxConfig = func(cfg host.Config, socketPath string, runtimeDir string) (process.Config, error) { return process.Config{ Path: cfg.Path, Env: map[string]string{ "OASIS_WORKER_HOST": socketPath, }, - } + }, nil } } // Use a default HostInitializer if none was provided. diff --git a/go/runtime/host/sgx/sgx.go b/go/runtime/host/sgx/sgx.go index 21823e7dbb5..b45aefa6f95 100644 --- a/go/runtime/host/sgx/sgx.go +++ b/go/runtime/host/sgx/sgx.go @@ -2,9 +2,13 @@ package sgx import ( + "bytes" "context" "encoding/binary" "fmt" + "io" + "io/ioutil" + "path/filepath" "sync" "time" @@ -12,8 +16,10 @@ import ( "github.com/oasislabs/oasis-core/go/common/cbor" "github.com/oasislabs/oasis-core/go/common/logging" "github.com/oasislabs/oasis-core/go/common/node" + "github.com/oasislabs/oasis-core/go/common/sgx" "github.com/oasislabs/oasis-core/go/common/sgx/aesm" cmnIAS "github.com/oasislabs/oasis-core/go/common/sgx/ias" + "github.com/oasislabs/oasis-core/go/common/sgx/sigstruct" "github.com/oasislabs/oasis-core/go/common/version" ias "github.com/oasislabs/oasis-core/go/ias/api" "github.com/oasislabs/oasis-core/go/runtime/host" @@ -26,7 +32,8 @@ const ( // TODO: Support different locations for the AESMD socket. aesmdSocketPath = "/var/run/aesmd/aesm.socket" - sandboxMountRuntime = "/runtime" + sandboxMountRuntime = "/runtime" + sandboxMountSignature = "/runtime.sig" // Runtime RAK initialization timeout. // @@ -53,6 +60,12 @@ type Config struct { InsecureNoSandbox bool } +// RuntimeExtra is the extra configuration for SGX runtimes. +type RuntimeExtra struct { + // SignaturePath is the path to the runtime (enclave) SIGSTRUCT. + SignaturePath string +} + type teeState struct { runtimeID common.Namespace eventEmitter host.RuntimeEventEmitter @@ -74,10 +87,58 @@ type sgxProvisioner struct { logger *logging.Logger } -func (s *sgxProvisioner) getSandboxConfig(rtCfg host.Config, socketPath string, runtimeDir string) process.Config { - runtimePath := rtCfg.Path - if !s.cfg.InsecureNoSandbox { - runtimePath = sandboxMountRuntime +func loadEnclaveBinaries(rtCfg host.Config) ([]byte, []byte, error) { + var ( + sig, sgxs []byte + enclaveHash sgx.MrEnclave + err error + ) + + if sgxs, err = ioutil.ReadFile(rtCfg.Path); err != nil { + return nil, nil, fmt.Errorf("failed to load enclave: %w", err) + } + if err = enclaveHash.FromSgxsBytes(sgxs); err != nil { + return nil, nil, fmt.Errorf("failed to derive EnclaveHash: %w", err) + } + + // If the path to an existing SIGSTRUCT is provided, load it. + if rtExtra, ok := rtCfg.Extra.(*RuntimeExtra); ok { + sig, err = ioutil.ReadFile(rtExtra.SignaturePath) + if err != nil { + return nil, nil, fmt.Errorf("failed to load SIGSTRUCT: %w", err) + } + } else { + // HACK: Just generate one with the test key for now. + if sig, err = sigstruct.UnsafeDebugForEnclave(sgxs); err != nil { + return nil, nil, fmt.Errorf("failed to generate debug SIGSTRUCT: %w", err) + } + } + + _, parsed, err := sigstruct.Verify(sig) + if err != nil { + return nil, nil, fmt.Errorf("failed to validate SIGSTRUCT: %w", err) + } + if parsed.EnclaveHash != enclaveHash { + return nil, nil, fmt.Errorf("enclave/SIGSTRUCT mismatch") + } + + return sgxs, sig, nil +} + +func (s *sgxProvisioner) getSandboxConfig(rtCfg host.Config, socketPath string, runtimeDir string) (process.Config, error) { + // To try to avoid bad things from happening if the signature/enclave + // binaries change out from under us, and because the enclave binary + // needs to be loaded into memory anyway, this always injects + // (or copies). + runtimePath, signaturePath := sandboxMountRuntime, sandboxMountSignature + if s.cfg.InsecureNoSandbox { + runtimePath = filepath.Join(runtimeDir, runtimePath) + signaturePath = filepath.Join(runtimeDir, signaturePath) + } + + sgxs, sig, err := loadEnclaveBinaries(rtCfg) + if err != nil { + return process.Config{}, fmt.Errorf("host/sgx: failed to load enclave/signature: %w", err) } return process.Config{ @@ -85,11 +146,9 @@ func (s *sgxProvisioner) getSandboxConfig(rtCfg host.Config, socketPath string, Args: []string{ "--host-socket", socketPath, "--type", "sgxs", + "--signature", signaturePath, runtimePath, }, - BindRO: map[string]string{ - rtCfg.Path: sandboxMountRuntime, - }, BindRW: map[string]string{ aesmdSocketPath: "/var/run/aesmd/aesm.socket", }, @@ -97,7 +156,11 @@ func (s *sgxProvisioner) getSandboxConfig(rtCfg host.Config, socketPath string, // TODO: Support different kinds of SGX drivers. "/dev/isgx": "/dev/isgx", }, - } + BindData: map[string]io.Reader{ + runtimePath: bytes.NewReader(sgxs), + signaturePath: bytes.NewReader(sig), + }, + }, nil } func (s *sgxProvisioner) hostInitializer(