Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AB#2432: Check for unsupported eclient import #172

Merged
merged 3 commits into from
Sep 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions ego/cli/elf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Copyright (c) Edgeless Systems GmbH.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

package cli

import (
"debug/elf"
"encoding/binary"
"errors"
"fmt"
"io"
"os"
"strings"
)

// ErrErrUnsupportedImportEClient is returned when an EGo binary uses the eclient package instead of the enclave package.
var ErrUnsupportedImportEClient = errors.New("unsupported import: github.com/edgelesssys/ego/eclient")

func (c *Cli) embedConfigAsPayload(path string, jsonData []byte) error {
// Load ELF executable
f, err := c.fs.OpenFile(path, os.O_RDWR, 0)
if err != nil {
return err
}
defer f.Close()

// Check if a payload already exists
payloadSize, payloadOffset, oeInfoOffset, err := getPayloadInformation(f)
if err != nil {
return err
}

// If a payload already exists, truncate the file to remove it
if payloadSize > 0 {
fileStat, err := f.Stat()
if err != nil {
return err
}

// Check if payload is at expected location
expectedPayloadOffset := fileStat.Size() - int64(payloadSize)
if expectedPayloadOffset != payloadOffset {
return errors.New("expected payload location does not match real payload location, cannot safely truncate old payload")
}

err = f.Truncate(payloadOffset)
if err != nil {
return err
}
} else if (payloadSize == 0) != (payloadOffset == 0) {
return errors.New("payload information in header is inconsistent, cannot continue")
}

// Get current file size to determine offset
fileStat, err := f.Stat()
if err != nil {
return err
}
filesize := fileStat.Size()

// Write payload offset and size to .oeinfo header
if err := writeUint64At(f, uint64(filesize), oeInfoOffset+2048); err != nil {
return err
}
if err := writeUint64At(f, uint64(len(jsonData)), oeInfoOffset+2056); err != nil {
return err
}

// And finally, append the payload to the file
n, err := f.WriteAt(jsonData, filesize)
if err != nil {
return err
} else if n != len(jsonData) {
return errors.New("failed to embed enclave.json as metadata")
}

return nil
}

func getPayloadInformation(f io.ReaderAt) (uint64, int64, int64, error) {
// .oeinfo + 2056 contains the size of an embedded Edgeless RT data payload.
// If it is > 0, a payload already exists.

elfFile, err := elf.NewFile(f)
if err != nil {
return 0, 0, 0, err
}

oeInfo := elfFile.Section(".oeinfo")
if oeInfo == nil {
return 0, 0, 0, ErrNoOEInfo
}

payloadOffset, err := readUint64At(oeInfo, 2048)
if err != nil {
return 0, 0, 0, err
}
payloadSize, err := readUint64At(oeInfo, 2056)
if err != nil {
return 0, 0, 0, err
}

return payloadSize, int64(payloadOffset), int64(oeInfo.Offset), nil
}

// checkUnsupportedImports checks whether the to-be-signed or to-be-executed binary uses Go imports which are not supported.
func (c *Cli) checkUnsupportedImports(path string) error {
// Load ELF executable
file, err := c.fs.OpenFile(path, os.O_RDONLY, 0)
if err != nil {
return err
}

elfFile, err := elf.NewFile(file)
Nirusu marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}
defer elfFile.Close()

// Check imports based on symbols in the ELF file
symbols, err := elfFile.Symbols()
if err != nil {
return fmt.Errorf("cannot read symbol table from given ELF binary: %w", err)
}

// Iterate through all symbols and find whether it matches a known unsupported one
for _, symbol := range symbols {
if strings.Contains(symbol.Name, "github.com/edgelesssys/ego/eclient") {
return ErrUnsupportedImportEClient
}
}

return nil
}

func writeUint64At(w io.WriterAt, x uint64, off int64) error {
xByte := make([]byte, 8)
binary.LittleEndian.PutUint64(xByte, x)

n, err := w.WriteAt(xByte, off)
if err != nil {
return err
} else if n != 8 {
return errors.New("did not write expected number of bytes")
}

return nil
}

func readUint64At(r io.ReaderAt, off int64) (uint64, error) {
xByte := make([]byte, 8)

n, err := r.ReadAt(xByte, off)
if err != nil {
return 0, err
} else if n != 8 {
return 0, errors.New("did not read expected number of bytes")
}

return binary.LittleEndian.Uint64(xByte), nil
}
151 changes: 151 additions & 0 deletions ego/cli/elf_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Copyright (c) Edgeless Systems GmbH.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

package cli

import (
"encoding/json"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"testing"

"ego/config"

"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// create an unsigned EGo executable
var elfUnsigned = func() []byte {
const outFile = "hello"
const srcFile = outFile + ".go"

goroot, err := filepath.Abs(filepath.Join("..", "..", "_ertgo"))
if err != nil {
panic(err)
}

dir, err := ioutil.TempDir("", "")
if err != nil {
panic(err)
}
defer os.RemoveAll(dir)

// write minimal source file
const src = `package main;import _"time";func main(){}`
if err := ioutil.WriteFile(filepath.Join(dir, srcFile), []byte(src), 0o400); err != nil {
panic(err)
}

// compile
cmd := exec.Command(filepath.Join(goroot, "bin", "go"), "build", srcFile)
cmd.Dir = dir
cmd.Env = append(os.Environ(), "GOROOT="+goroot)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
panic(err)
}

// read resulting executable
data, err := ioutil.ReadFile(filepath.Join(dir, outFile))
if err != nil {
panic(err)
}

return data
}()

func TestEmbedConfigAsPayload(t *testing.T) {
require := require.New(t)
assert := assert.New(t)

// Setup test environment
fs := afero.Afero{Fs: afero.NewMemMapFs()}
runner := signRunner{fs: fs}
cli := NewCli(&runner, fs)

// Copy from hostfs to memfs
const exe = "helloworld"
require.NoError(fs.WriteFile(exe, elfUnsigned, 0))

// Check if no payload exists
unsignedExeMemfs, err := fs.Open(exe)
require.NoError(err)
unsignedExeMemfsStat, err := unsignedExeMemfs.Stat()
require.NoError(err)
unsignedExeMemfsSize := unsignedExeMemfsStat.Size()
payloadSize, payloadOffset, oeInfoOffset, err := getPayloadInformation(unsignedExeMemfs)
assert.NoError(err)
assert.Zero(payloadSize)
assert.Zero(payloadOffset)
assert.NotZero(oeInfoOffset)
unsignedExeMemfs.Close()

// Create a default config we want to check
testConf := &config.Config{
Exe: exe,
Key: defaultPrivKeyFilename,
Debug: true,
HeapSize: 512, //[MB]
ProductID: 1,
SecurityVersion: 1,
}

// Marshal config
jsonData, err := json.Marshal(testConf)
require.NoError(err)
expectedLengthOfPayload := len(jsonData)

// Embed json config to helloworld
err = cli.embedConfigAsPayload(exe, jsonData)
assert.NoError(err)

// Check if new helloworld_signed now contains signed data
signedExeMemfs, err := fs.Open(exe)
require.NoError(err)
defer signedExeMemfs.Close()

payloadSize, payloadOffset, oeInfoOffset, err = getPayloadInformation(signedExeMemfs)
assert.NoError(err)
assert.EqualValues(expectedLengthOfPayload, payloadSize)
assert.EqualValues(unsignedExeMemfsSize, payloadOffset)
assert.NotZero(oeInfoOffset)

// Reconstruct the JSON and check equality
reconstructedJSON := make([]byte, payloadSize)
n, err := signedExeMemfs.ReadAt(reconstructedJSON, payloadOffset)
require.NoError(err)
require.EqualValues(payloadSize, n)
assert.EqualValues(jsonData, reconstructedJSON)

// Now modify the config, redo everything and see if trucate worked fine and everything still lines up
testConf.HeapSize = 5120
jsonNewData, err := json.Marshal(testConf)
require.NoError(err)
expectedLengthOfNewPayload := len(jsonNewData)

// Re-sign the already signed executable
err = cli.embedConfigAsPayload(exe, jsonNewData)
assert.NoError(err)
payloadSize, payloadOffset, _, err = getPayloadInformation(signedExeMemfs)
require.NoError(err)
assert.EqualValues(expectedLengthOfNewPayload, payloadSize)
assert.EqualValues(unsignedExeMemfsSize, payloadOffset)

// Reconstruct the JSON and check if it not the old one, but the new one
reconstructedJSON = make([]byte, payloadSize)
n, err = signedExeMemfs.ReadAt(reconstructedJSON, payloadOffset)
require.NoError(err)
require.EqualValues(payloadSize, n)

// Finally, check if we got the new JSON config and not the old one
assert.NotEqualValues(jsonData, reconstructedJSON)
assert.EqualValues(jsonNewData, reconstructedJSON)
}
6 changes: 6 additions & 0 deletions ego/cli/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@ import (

// Run runs a signed executable in standalone mode.
func (c *Cli) Run(filename string, args []string) (int, error) {
if err := c.checkUnsupportedImports(filename); err != nil {
return 1, err
}
return launch.RunEnclave(filename, args, c.getEgoHostPath(), c.getEgoEnclavePath(), c.runner)
}

// Marblerun runs a signed executable as a MarbleRun Marble.
func (c *Cli) Marblerun(filename string) (int, error) {
if err := c.checkUnsupportedImports(filename); err != nil {
return 1, err
}
return launch.RunEnclaveMarblerun(filename, c.getEgoHostPath(), c.getEgoEnclavePath(), c.runner)
}

Expand Down
Loading