Skip to content

Commit

Permalink
Add guest package for fetching attestation report via syscall
Browse files Browse the repository at this point in the history
Add ioctl wrapper and structs required to fetch attestation report.
Add a command line tool to fetch real and fake attestation
reports.
Fake attestation report can be used when testing integrations.

Validate that LaunchData provided to HCS and HostData returned as
part of attestation report match.

Signed-off-by: Maksim An <[email protected]>
  • Loading branch information
anmaxvl committed Mar 31, 2022
1 parent 93505d7 commit 67629d0
Show file tree
Hide file tree
Showing 8 changed files with 475 additions and 30 deletions.
10 changes: 10 additions & 0 deletions internal/guest/runtime/hcsv2/uvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"syscall"
"time"

"github.com/Microsoft/hcsshim/internal/guest/snp"
"github.com/mattn/go-shellwords"
"github.com/pkg/errors"

Expand Down Expand Up @@ -95,6 +96,15 @@ func (h *Host) SetSecurityPolicy(base64Policy string) error {
return err
}

hostData, err := securitypolicy.NewSecurityPolicyDigest(base64Policy)
if err != nil {
return err
}

if err := snp.ValidateHostData(hostData, ""); err != nil {
return err
}

h.securityPolicyEnforcer = p
h.securityPolicyEnforcerSet = true

Expand Down
36 changes: 36 additions & 0 deletions internal/guest/snp/fake_report.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//go:build linux
// +build linux

package snp

import (
"bytes"
"encoding/binary"
"encoding/hex"
)

const fakeSNPReport = "01000000010000001f00030000000000010000000000000000000000000000000200000000000000000000000000000000000000010000000000000000000028010000000000000000000000000000007ab000a323b3c873f5b81bbe584e7c1a26bcf40dc27e00f8e0d144b1ed2d14f10000000000000000000000000000000000000000000000000000000000000000e29af700e85b39996fa38226d2804b78cad746ffef4477360a61b47874bdecd640f9d32f5ff64a55baad3c545484d9ed28603a3ea835a83bd688b0ec1dcb36b6b8c22412e5b63115b75db8628b989bc598c475ca5f7683e8d351e7e789a1baff19041750567161ad52bf0d152bd76d7c6f313d0a0fd72d0089692c18f521155800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040aea62690b08eb6d680392c9a9b3db56a9b3cc44083b9da31fb88bcfc493407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000028000000000000000000000000000000000000000000000000e6c86796cd44b0bc6b7c0d4fdab33e2807e14b5fc4538b3750921169d97bcf4447c7d3ab2a7c25f74c1641e2885c1011d025cc536f5c9a2504713136c7877f480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003131c0f3e7be5c6e400f22404596e1874381e99d03de45ef8b97eee0a0fa93a4911550330343f14dddbbd6c0db83744f000000000000000000000000000000000000000000000000db07c83c5e6162c2387f3b76cd547672657f6a5df99df98efee7c15349320d83e086c5003ec43050a9b18d1c39dedc340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"

func fakeGetSNPReport() (snpReport, error) {
reportBytes, err := hex.DecodeString(fakeSNPReport)
if err != nil {
return snpReport{}, err
}

report := snpReport{}
if err := binary.Read(bytes.NewBuffer(reportBytes), binary.LittleEndian, &report); err != nil {
return snpReport{}, err
}
return report, nil
}

func FetchFakeSNPReport(hostData string) (Report, error) {
fakeRaw, err := fakeGetSNPReport()
if err != nil {
return Report{}, err
}
if hostData != "" {
copy(fakeRaw.HostData[:], hostData)
}
return convertRawReport(&fakeRaw), nil
}
321 changes: 321 additions & 0 deletions internal/guest/snp/report.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
//go:build linux
// +build linux

// package snp contains minimal functionality required to fetch
// attestation reports inside an enlightened guest.
package snp

import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"os"
"unsafe"

"golang.org/x/sys/unix"
)

const (
MsgTypeInvalid = iota
MsgCPUIDRequest
MsgCPUIDResponse
MsgKeyRequest
MsgKeyResponse
MsgReportRequest
MsgReportResponse
MsgExportRequest
MsgExportResponse
MsgImportRequest
MsgImportResponse
MsgAbsorbRequest
MsgAbsorbResponse
MsgVMRKRequest
MsgVMRKResponse
MsgTypeMax
)

type SevSNPGuestRequest struct {
RequestMsgType byte
ResponseMsgType byte
MsgVersion byte
RequestLength uint16
RequestUAddr uint64
ResponseLength uint16
ResponseUAddr uint64
Error uint32
}

// Below is ported from Linux kernel code:
// https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/include/uapi/asm-generic/ioctl.h
const (
iocNone = 0
iocWrite = 1
iocRead = 2
iocNRBits = 8
iocTypeBits = 8
iocSizeBits = 14
iocDirBits = 2

// bit shifts
iocNRShift = 0
iocTypeShift = iocNRBits
iocSizeShift = iocTypeShift + iocTypeBits
iocDirShift = iocSizeShift + iocSizeBits

// SEV-SNP IOCTL type
sevGuestType = 'S'
// SEV-SNP IOCTL size, same as unsafe.Sizeof(SevSNPGuestRequest{})
sevGuestSize = 40

// SEV-SNP requests
sevSNPReportCode = 0x1
)

// _IOC macros equivalent
func ioc(dir, t, nr, size int) int {
return dir<<iocDirShift | t<<iocTypeShift | nr<<iocNRShift | size<<iocSizeShift
}

// _IOCWR macros equivalent
func iocWR(t, nr, size int) int {
return ioc(iocRead|iocWrite, t, nr, size)
}

// msgReportRequest used to issue SEV-SNP request
// https://www.amd.com/system/files/TechDocs/56860.pdf
// MSG_REPORT_REQ Table 20.
type msgReportRequest struct {
ReportData [64]byte
VMPL uint32
_ [28]byte
}

// msgReportRequest used to issue SEV-SNP request
// https://www.amd.com/system/files/TechDocs/56860.pdf
// ATTESTATION_REPORT Table 21.
type snpReport struct {
Version uint32
GuestSVN uint32
Policy uint64
FamilyID [16]byte
ImageID [16]byte
VMPL uint32
SignatureAlgo uint32
PlatformVersion uint64
PlatformInfo uint64
AuthorKeyEn uint32
Reserved1 uint32
ReportData [64]byte
Measurement [48]byte
HostData [32]byte
IDKeyDigest [48]byte
AuthorKeyDigest [48]byte
ReportID [32]byte
ReportIDMA [32]byte
ReportTCB uint64
Reserved2 [24]byte
ChipID [64]byte
CommittedSVN [8]byte
CommittedVersion [8]byte
LaunchSVN [8]byte
Reserved3 [168]byte
Signature [512]byte
}

// msgReportResponse is the attestation response struct
// https://www.amd.com/system/files/TechDocs/56860.pdf
// MSG_REPORT_RSP Table 23.
// NOTE: msgReportResponse.Report is a byte slice, to have the original
// response in bytes. The conversion to internal struct happens inside
// convertRawReport.
// NOTE: the additional 64 bytes are reserved, without them, the ioctl fails.
type msgReportResponse struct {
Status uint32
ReportSize uint32
reserved1 [24]byte
Report [1184]byte
reserved2 [64]byte // padding to the size of SEV_SNP_REPORT_RSP_BUF_SZ (i.e., 1280 bytes)
}

func snpIoctl(f *os.File, code int, payload *SevSNPGuestRequest) error {
sevSNPIOCBase := iocWR(sevGuestType, 0x0, sevGuestSize)
_, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(), uintptr(code|sevSNPIOCBase), uintptr(unsafe.Pointer(payload)))
if errno != 0 {
return fmt.Errorf("failed to make snp IOCTL: %s", errno)
}
return nil
}

// FetchRawSNPReport returns attestation report bytes.
func FetchRawSNPReport(reportData string) ([]byte, error) {
f, err := os.OpenFile("/dev/sev", os.O_RDWR, 0)
if err != nil {
return nil, err
}
defer func() {
f.Close()
}()

msgReportIn := msgReportRequest{}
msgReportOut := msgReportResponse{}

if reportData != "" {
if len(reportData) > len(msgReportIn.ReportData) {
return nil, fmt.Errorf("reportData too large: %s", reportData)
}
rd, err := hex.DecodeString(reportData)
if err != nil {
return nil, err
}
copy(msgReportIn.ReportData[:], rd[:])
}

payload := SevSNPGuestRequest{
RequestMsgType: MsgReportRequest,
ResponseMsgType: MsgReportResponse,
MsgVersion: 1,
RequestLength: uint16(unsafe.Sizeof(msgReportIn)),
RequestUAddr: uint64(uintptr(unsafe.Pointer(&msgReportIn))),
ResponseLength: uint16(unsafe.Sizeof(msgReportOut)),
ResponseUAddr: uint64(uintptr(unsafe.Pointer(&msgReportOut))),
Error: 0,
}

if err := snpIoctl(f, sevSNPReportCode, &payload); err != nil {
return nil, err
}
return msgReportOut.Report[:], nil
}

// Report represents parsed attestation report.
type Report struct {
Version uint32
GuestSVN uint32
Policy uint64
FamilyID string
ImageID string
VMPL uint32
SignatureAlgo uint32
PlatformVersion uint64
PlatformInfo uint64
AuthorKeyEn uint32
ReportData string
Measurement string
HostData string
IDKeyDigest string
AuthorKeyDigest string
ReportID string
ReportIDMA string
ReportTCB uint64
ChipID string
CommittedSVN string
CommittedVersion string
LaunchSVN string
Signature string
}

// PrettyString returns formatted attestation report.
func (r Report) PrettyString() string {
fieldNameFmt := "%-20s"
pretty := ""
pretty += fmt.Sprintf(fieldNameFmt+"%08x\n", "Version", r.Version)
pretty += fmt.Sprintf(fieldNameFmt+"%08x\n", "GuestSVN", r.GuestSVN)
pretty += fmt.Sprintf(fieldNameFmt+"%016x\n", "Policy", r.Policy)
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "FamilyID", r.FamilyID)
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "ImageID", r.ImageID)
pretty += fmt.Sprintf(fieldNameFmt+"%08x\n", "VMPL", r.VMPL)
pretty += fmt.Sprintf(fieldNameFmt+"%08x\n", "SignatureAlgo", r.SignatureAlgo)
pretty += fmt.Sprintf(fieldNameFmt+"%016x\n", "PlatformVersion", r.PlatformVersion)
pretty += fmt.Sprintf(fieldNameFmt+"%016x\n", "PlatformInfo", r.PlatformInfo)
pretty += fmt.Sprintf(fieldNameFmt+"%08x\n", "AuthorKeyEn", r.AuthorKeyEn)
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "ReportData", r.ReportData)
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "Measurement", r.Measurement)
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "HostData", r.HostData)
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "IDKeyDigest", r.IDKeyDigest)
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "AuthorKeyDigest", r.AuthorKeyDigest)
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "ReportID", r.ReportID)
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "ReportIDMA", r.ReportIDMA)
pretty += fmt.Sprintf(fieldNameFmt+"%016x\n", "ReportTCB", r.ReportTCB)
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "ChipID", r.ChipID)
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "CommittedSVN", r.CommittedSVN)
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "CommittedVersion", r.CommittedVersion)
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "LaunchSVN", r.LaunchSVN)
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "Signature", r.Signature)
return pretty
}

// mirrorBytes mirrors the byte ordering so that hex-encoding little endian
// ordered bytes come out in the readable order.
func mirrorBytes(b []byte) []byte {
for i := 0; i < len(b)/2; i++ {
mirrorIndex := len(b) - i - 1
b[i], b[mirrorIndex] = b[mirrorIndex], b[i]
}
return b
}

func convertRawReport(rawReport *snpReport) Report {
return Report{
Version: rawReport.Version,
GuestSVN: rawReport.GuestSVN,
Policy: rawReport.Policy,
FamilyID: hex.EncodeToString(mirrorBytes(rawReport.FamilyID[:])[:]),
ImageID: hex.EncodeToString(mirrorBytes(rawReport.ImageID[:])[:]),
VMPL: rawReport.VMPL,
SignatureAlgo: rawReport.SignatureAlgo,
PlatformVersion: rawReport.PlatformVersion,
PlatformInfo: rawReport.PlatformInfo,
AuthorKeyEn: rawReport.AuthorKeyEn,
ReportData: hex.EncodeToString(rawReport.ReportData[:]),
Measurement: hex.EncodeToString(rawReport.Measurement[:]),
HostData: hex.EncodeToString(rawReport.HostData[:]),
IDKeyDigest: hex.EncodeToString(rawReport.IDKeyDigest[:]),
AuthorKeyDigest: hex.EncodeToString(rawReport.AuthorKeyDigest[:]),
ReportID: hex.EncodeToString(rawReport.ReportID[:]),
ReportIDMA: hex.EncodeToString(rawReport.ReportIDMA[:]),
ReportTCB: rawReport.ReportTCB,
ChipID: hex.EncodeToString(rawReport.ChipID[:]),
CommittedSVN: hex.EncodeToString(rawReport.CommittedSVN[:]),
CommittedVersion: hex.EncodeToString(rawReport.CommittedVersion[:]),
LaunchSVN: hex.EncodeToString(rawReport.LaunchSVN[:]),
Signature: hex.EncodeToString(rawReport.Signature[:]),
}
}

// FetchParsedSNPReport parses raw attestation response into proper structs.
func FetchParsedSNPReport(reportData string) (Report, error) {
rawBytes, err := FetchRawSNPReport(reportData)
if err != nil {
return Report{}, err
}

var report snpReport
buf := bytes.NewBuffer(rawBytes)
if err := binary.Read(buf, binary.LittleEndian, &report); err != nil {
return Report{}, err
}
return convertRawReport(&report), nil
}

// ValidateHostData fetches SNP report (if applicable) and validates `hostData` against
// HostData set at UVM launch.
func ValidateHostData(hostData, reportData string) error {
report, err := FetchParsedSNPReport(reportData)
if err != nil {
// For non-SNP hardware /dev/sev will not exist
if os.IsNotExist(err) {
return nil
}
return err
}

if report.HostData != hostData {
return fmt.Errorf(
"security policy digest %q doesn't match HostData provided at launch %q",
hostData,
report.HostData,
)
}
return nil
}
Loading

0 comments on commit 67629d0

Please sign in to comment.