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

feat(cmd/rofl): Add support for building TDX ROFL apps #298

Merged
merged 3 commits into from
Nov 29, 2024
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
4 changes: 2 additions & 2 deletions .github/workflows/ci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.21.x"
go-version: "1.22.x"
cache: false
- name: Install gitlint
run: |
Expand All @@ -63,7 +63,7 @@ jobs:
# 'make lint-go'.
uses: golangci/[email protected]
with:
version: v1.55
version: v1.56
# Always run this step so that all linting errors can be seen at once.
if: always()
- name: Ensure a clean code checkout
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.21.x"
go-version: "1.22.x"
- name: Cache Go dependencies
uses: actions/cache@v4
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.21.x"
go-version: "1.22.x"
- name: Install GoReleaser
uses: goreleaser/goreleaser-action@v5
with:
Expand Down
3 changes: 3 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ linters-settings:
- github.com/stretchr/testify
- github.com/tyler-smith/go-bip39
- github.com/zondax/ledger-go
- github.com/foxboron/go-uefi/authenticode
- golang.org/x/text
- gopkg.in/yaml.v3
exhaustive:
# Switch statements are to be considered exhaustive if a 'default' case is
# present, even if all enum members aren't listed in the switch.
Expand Down
53 changes: 46 additions & 7 deletions build/cargo/cargo.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,44 @@ import (
"encoding/json"
"fmt"
"os/exec"
"slices"
"strings"
)

// Metadata is the cargo package metadata.
type Metadata struct {
Name string
Version string
Name string
Version string
Dependencies []Dependency
}

// FindDependency finds the first dependency with the given name and returns it. Iff no such
// dependency can be found, it returns nil.
func (m *Metadata) FindDependency(name string) *Dependency {
for _, d := range m.Dependencies {
if d.Name != name {
continue
}

return &d
}
return nil
}

// Dependency is the metadata about a dependency.
type Dependency struct {
Name string `json:"name"`
Features []string `json:"features"`
}

// HasFeature returns true iff the given feature is present among the features.
func (d *Dependency) HasFeature(feature string) bool {
return slices.Contains(d.Features, feature)
}

// GetMetadata queries `cargo` for metadata of the package in the current working directory.
func GetMetadata() (*Metadata, error) {
cmd := exec.Command("cargo", "metadata", "--no-deps")
cmd := exec.Command("cargo", "metadata", "--no-deps", "--format-version", "1")
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, fmt.Errorf("failed to initialize metadata process: %w", err)
Expand All @@ -29,8 +55,12 @@ func GetMetadata() (*Metadata, error) {
dec := json.NewDecoder(stdout)
var rawMeta struct {
Packages []struct {
Name string `json:"name"`
Version string `json:"version"`
Name string `json:"name"`
Version string `json:"version"`
Dependencies []struct {
Name string `json:"name"`
Features []string `json:"features"`
} `json:"dependencies"`
} `json:"packages"`
}
if err = dec.Decode(&rawMeta); err != nil {
Expand All @@ -43,10 +73,19 @@ func GetMetadata() (*Metadata, error) {
return nil, fmt.Errorf("no cargo packages found")
}

return &Metadata{
meta := &Metadata{
Name: rawMeta.Packages[0].Name,
Version: rawMeta.Packages[0].Version,
}, nil
}
for _, dep := range rawMeta.Packages[0].Dependencies {
d := Dependency{
Name: dep.Name,
Features: dep.Features,
}
meta.Dependencies = append(meta.Dependencies, d)
}

return meta, nil
}

// Build builds a Rust program using `cargo` in the current working directory.
Expand Down
223 changes: 223 additions & 0 deletions build/measurement/acpi/acpi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
package acpi

import (
"bytes"
"embed"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"

"github.com/oasisprotocol/oasis-core/go/runtime/bundle"
)

//go:embed *.hex *.json
var templates embed.FS

// OffsetData is the offset data file format.
type OffsetData struct {
Memory MemoryOffsetData `json:"memory"`
}

type MemoryOffsetData struct {
RangeMinimumOffset int `json:"range_minimum_offset"`
LengthOffset int `json:"length_offset"`
}

// GenerateTablesQemu generates ACPI tables for the given TD configuration.
//
// Returns the raw ACPI tables, RSDP and QEMU table loader command blob.
func GenerateTablesQemu(resources *bundle.TDXResources) ([]byte, []byte, []byte, error) {
// Fetch template based on CPU count.
fn := fmt.Sprintf("template_qemu_cpu%d.hex", resources.CPUCount)
tplHex, err := templates.ReadFile(fn)
if err != nil {
return nil, nil, nil, fmt.Errorf("template for ACPI tables is not available")
}

tpl, err := hex.DecodeString(string(tplHex))
if err != nil {
return nil, nil, nil, fmt.Errorf("malformed ACPI table template")
}

// Fetch corresponding offset data.
fn = fmt.Sprintf("template_qemu_cpu%d.json", resources.CPUCount)
offsetData, err := templates.ReadFile(fn)
if err != nil {
return nil, nil, nil, fmt.Errorf("offset data for ACPI tables is not available")
}

var od OffsetData
if err = json.Unmarshal(offsetData, &od); err != nil {
return nil, nil, nil, fmt.Errorf("malformed ACPI table offset data")
}

// Handle memory split at 2816 MiB (0xB0000000).
if resources.Memory >= 2816 {
binary.LittleEndian.PutUint32(tpl[od.Memory.RangeMinimumOffset:], 0x80000000)
binary.LittleEndian.PutUint32(tpl[od.Memory.LengthOffset:], 0x60000000)
} else {
memSizeBytes := uint32(resources.Memory * 1024 * 1024)
binary.LittleEndian.PutUint32(tpl[od.Memory.RangeMinimumOffset:], memSizeBytes)
binary.LittleEndian.PutUint32(tpl[od.Memory.LengthOffset:], 0xe0000000-memSizeBytes)
}

// Generate RSDP.
rsdp := append([]byte{},
0x52, 0x53, 0x44, 0x20, 0x50, 0x54, 0x52, 0x20, // Signature ("RSDP PTR ").
0x00, // Checksum.
0x42, 0x4F, 0x43, 0x48, 0x53, 0x20, // OEM ID ("BOCHS ").
0x00, // Revision.
)

// Find all required ACPI tables.
dsdtOffset, dsdtCsum, dsdtLen, err := findAcpiTable(tpl, "DSDT")
if err != nil {
return nil, nil, nil, err
}
facpOffset, facpCsum, facpLen, err := findAcpiTable(tpl, "FACP")
if err != nil {
return nil, nil, nil, err
}
apicOffset, apicCsum, apicLen, err := findAcpiTable(tpl, "APIC")
if err != nil {
return nil, nil, nil, err
}
mcfgOffset, mcfgCsum, mcfgLen, err := findAcpiTable(tpl, "MCFG")
if err != nil {
return nil, nil, nil, err
}
waetOffset, waetCsum, waetLen, err := findAcpiTable(tpl, "WAET")
if err != nil {
return nil, nil, nil, err
}
rsdtOffset, rsdtCsum, rsdtLen, err := findAcpiTable(tpl, "RSDT")
if err != nil {
return nil, nil, nil, err
}

// Update RSDP with RSDT address.
var rsdtAddress [4]byte
binary.LittleEndian.PutUint32(rsdtAddress[:], rsdtOffset)
rsdp = append(rsdp, rsdtAddress[:]...)

// Generate table loader commands.
const ldrLength = 4096
ldr := qemuLoaderAppend(nil, &qemuLoaderCmdAllocate{"etc/acpi/rsdp", 16, 2})
ldr = qemuLoaderAppend(ldr, &qemuLoaderCmdAllocate{"etc/acpi/tables", 64, 1})
ldr = qemuLoaderAppend(ldr, &qemuLoaderCmdAddChecksum{"etc/acpi/tables", dsdtCsum, dsdtOffset, dsdtLen}) // DSDT
ldr = qemuLoaderAppend(ldr, &qemuLoaderCmdAddPtr{"etc/acpi/tables", "etc/acpi/tables", facpOffset + 36, 4})
ldr = qemuLoaderAppend(ldr, &qemuLoaderCmdAddPtr{"etc/acpi/tables", "etc/acpi/tables", facpOffset + 40, 4})
ldr = qemuLoaderAppend(ldr, &qemuLoaderCmdAddPtr{"etc/acpi/tables", "etc/acpi/tables", facpOffset + 140, 8})
ldr = qemuLoaderAppend(ldr, &qemuLoaderCmdAddChecksum{"etc/acpi/tables", facpCsum, facpOffset, facpLen}) // FACP
ldr = qemuLoaderAppend(ldr, &qemuLoaderCmdAddChecksum{"etc/acpi/tables", apicCsum, apicOffset, apicLen}) // APIC
ldr = qemuLoaderAppend(ldr, &qemuLoaderCmdAddChecksum{"etc/acpi/tables", mcfgCsum, mcfgOffset, mcfgLen}) // MCFG
ldr = qemuLoaderAppend(ldr, &qemuLoaderCmdAddChecksum{"etc/acpi/tables", waetCsum, waetOffset, waetLen}) // WAET
ldr = qemuLoaderAppend(ldr, &qemuLoaderCmdAddPtr{"etc/acpi/tables", "etc/acpi/tables", rsdtOffset + 36, 4})
ldr = qemuLoaderAppend(ldr, &qemuLoaderCmdAddPtr{"etc/acpi/tables", "etc/acpi/tables", rsdtOffset + 40, 4})
ldr = qemuLoaderAppend(ldr, &qemuLoaderCmdAddPtr{"etc/acpi/tables", "etc/acpi/tables", rsdtOffset + 44, 4})
ldr = qemuLoaderAppend(ldr, &qemuLoaderCmdAddPtr{"etc/acpi/tables", "etc/acpi/tables", rsdtOffset + 48, 4})
ldr = qemuLoaderAppend(ldr, &qemuLoaderCmdAddChecksum{"etc/acpi/tables", rsdtCsum, rsdtOffset, rsdtLen}) // RSDT
ldr = qemuLoaderAppend(ldr, &qemuLoaderCmdAddPtr{"etc/acpi/rsdp", "etc/acpi/tables", 16, 4}) // RSDT address
ldr = qemuLoaderAppend(ldr, &qemuLoaderCmdAddChecksum{"etc/acpi/rsdp", 8, 0, 20}) // RSDP
if len(ldr) < ldrLength {
ldr = append(ldr, bytes.Repeat([]byte{0x00}, ldrLength-len(ldr))...)
}

return tpl, rsdp, ldr, nil
}

// findAcpiTable searches for the ACPI table with the given signature and returns its offset,
// checksum offset and length.
func findAcpiTable(tables []byte, signature string) (uint32, uint32, uint32, error) {
// Walk the tables to find the right one.
var offset int
for {
if offset >= len(tables) {
return 0, 0, 0, fmt.Errorf("ACPI table '%s' not found", signature)
}

tblSig := string(tables[offset : offset+4])
tblLen := int(binary.LittleEndian.Uint32(tables[offset+4 : offset+8]))
if tblSig == signature {
return uint32(offset), uint32(offset + 9), uint32(tblLen), nil
}

// Skip other tables.
offset += tblLen
}
}

type qemuLoaderCmdAllocate struct {
file string
alignment uint32
zone uint8
}

type qemuLoaderCmdAddPtr struct {
pointerFile string
pointeeFile string
pointerOffset uint32
pointerSize uint8
}

type qemuLoaderCmdAddChecksum struct {
file string
resultOffset uint32
start uint32
length uint32
}

func qemuLoaderAppend(data []byte, cmd interface{}) []byte {
appendFixedString := func(str string) {
const fixedLength = 56
data = append(data, []byte(str)...)
if len(str) < fixedLength {
data = append(data, bytes.Repeat([]byte{0x00}, fixedLength-len(str))...)
}
}

switch c := cmd.(type) {
case *qemuLoaderCmdAllocate:
data = append(data, 0x01, 0x00, 0x00, 0x00)

appendFixedString(c.file)

var val [4]byte
binary.LittleEndian.PutUint32(val[:], c.alignment)
data = append(data, val[:]...)

data = append(data, c.zone)
data = append(data, bytes.Repeat([]byte{0x00}, 63)...) // Padding.
case *qemuLoaderCmdAddPtr:
data = append(data, 0x02, 0x00, 0x00, 0x00)

appendFixedString(c.pointerFile)
appendFixedString(c.pointeeFile)

var val [4]byte
binary.LittleEndian.PutUint32(val[:], c.pointerOffset)
data = append(data, val[:]...)
data = append(data, c.pointerSize)
data = append(data, bytes.Repeat([]byte{0x00}, 7)...) // Padding.
case *qemuLoaderCmdAddChecksum:
data = append(data, 0x03, 0x00, 0x00, 0x00)

appendFixedString(c.file)

var val [4]byte
binary.LittleEndian.PutUint32(val[:], c.resultOffset)
data = append(data, val[:]...)

binary.LittleEndian.PutUint32(val[:], c.start)
data = append(data, val[:]...)

binary.LittleEndian.PutUint32(val[:], c.length)
data = append(data, val[:]...)

data = append(data, bytes.Repeat([]byte{0x00}, 56)...) // Padding.
default:
panic("unsupported command")
}
return data
}
1 change: 1 addition & 0 deletions build/measurement/acpi/template_qemu_cpu1.hex

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions build/measurement/acpi/template_qemu_cpu1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"memory": {
"range_minimum_offset": 7489,
"length_offset": 7501
}
}
1 change: 1 addition & 0 deletions build/measurement/acpi/template_qemu_cpu10.hex

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions build/measurement/acpi/template_qemu_cpu10.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"memory": {
"range_minimum_offset": 8261,
"length_offset": 8273
}
}
1 change: 1 addition & 0 deletions build/measurement/acpi/template_qemu_cpu11.hex

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions build/measurement/acpi/template_qemu_cpu11.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"memory": {
"range_minimum_offset": 8347,
"length_offset": 8359
}
}
1 change: 1 addition & 0 deletions build/measurement/acpi/template_qemu_cpu12.hex

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions build/measurement/acpi/template_qemu_cpu12.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"memory": {
"range_minimum_offset": 8433,
"length_offset": 8445
}
}
1 change: 1 addition & 0 deletions build/measurement/acpi/template_qemu_cpu13.hex

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions build/measurement/acpi/template_qemu_cpu13.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"memory": {
"range_minimum_offset": 8519,
"length_offset": 8531
}
}
1 change: 1 addition & 0 deletions build/measurement/acpi/template_qemu_cpu14.hex

Large diffs are not rendered by default.

Loading