Skip to content

Commit

Permalink
Fix "modelstepping" handling in verification
Browse files Browse the repository at this point in the history
The "Milan-B0" naming in a product name is not directly derivable from
the cpuid(1) values. Instead this B0 naming is from a different naming
convention that comes from the development lifecycle that the
manufacturer decides. Every stepping number that is visible to consumers
is subject to a table lookup. That table can only be built from a
piecemeal toilsome effort of following the manufacturers' publications
about its product lines.

This fix has been verified in hardware tests.

Signed-off-by: Dionna Glaze <[email protected]>
  • Loading branch information
deeglaze committed Sep 27, 2023
1 parent 307fceb commit 93c77e9
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 117 deletions.
11 changes: 6 additions & 5 deletions abi/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -818,11 +818,12 @@ func SevProduct() *pb.SevProduct {
// 15:14 reserved
// 11:8 Family ID
family := (eax >> 8) & 0xf
// 7:4 Model, 3:0 Stepping
modelStepping := eax & 0xff
// 3:0 Stepping
stepping := eax & 0xf
// Ah, Fh, {0h,1h} values from the KDS specification,
// section "Determining the Product Name".
var productName pb.SevProduct_SevProductName
// Product information specified by processor programming reference publications.
if extendedFamily == 0xA && family == 0xF {
switch extendedModel {
case 0:
Expand All @@ -834,12 +835,12 @@ func SevProduct() *pb.SevProduct {
}
}
return &pb.SevProduct{
Name: productName,
ModelStepping: modelStepping,
Name: productName,
Stepping: stepping,
}
}

// DefaultSevProduct returns the initial product version for a commercially available AMD SEV-SNP chip.
func DefaultSevProduct() *pb.SevProduct {
return &pb.SevProduct{Name: pb.SevProduct_SEV_PRODUCT_MILAN, ModelStepping: 0xB0}
return &pb.SevProduct{Name: pb.SevProduct_SEV_PRODUCT_MILAN, Stepping: 1}
}
76 changes: 50 additions & 26 deletions kds/kds.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,20 @@ var (
kdsBaseURL = "https://" + kdsHostname
kdsVcekPath = "/vcek/v1/"
kdsVlekPath = "/vlek/v1/"

// Chip manufacturers assign stepping versions strings that are <letter><number>
// to describe a stepping number for a particular model chip. There is no way
// other than documentation to map a stepping number to a stepping version and
// vice versa.
steppingDecoder = map[string]*pb.SevProduct{
"Milan-B0": {Name: pb.SevProduct_SEV_PRODUCT_MILAN, Stepping: 0},
"Milan-B1": {Name: pb.SevProduct_SEV_PRODUCT_MILAN, Stepping: 1},
"Genoa-B0": {Name: pb.SevProduct_SEV_PRODUCT_GENOA, Stepping: 0},
"Genoa-B1": {Name: pb.SevProduct_SEV_PRODUCT_GENOA, Stepping: 1},
"Genoa-B2": {Name: pb.SevProduct_SEV_PRODUCT_GENOA, Stepping: 2},
}
milanSteppingVersions = []string{"B0", "B1"}
genoaSteppingVersions = []string{"B0", "B1", "B2"}
)

// TCBVersion is a 64-bit bitfield of different security patch levels of AMD firmware and microcode.
Expand Down Expand Up @@ -692,45 +706,55 @@ func ProductName(product *pb.SevProduct) string {
if product == nil {
product = abi.DefaultSevProduct()
}
return fmt.Sprintf("%s-%02X", ProductString(product), product.ModelStepping)
if product.Stepping > 15 {
return "badstepping"
}
switch product.Name {
case pb.SevProduct_SEV_PRODUCT_MILAN:
if int(product.Stepping) >= len(milanSteppingVersions) {
return "unmappedMilanStepping"
}
return fmt.Sprintf("Milan-%s", milanSteppingVersions[product.Stepping])
case pb.SevProduct_SEV_PRODUCT_GENOA:
if int(product.Stepping) >= len(genoaSteppingVersions) {
return "unmappedGenoaStepping"
}
return fmt.Sprintf("Milan-%s", genoaSteppingVersions[product.Stepping])
default:
return "Unknown"
}
}

func parseProduct(product string) (pb.SevProduct_SevProductName, error) {
switch product {
case "Milan":
return pb.SevProduct_SEV_PRODUCT_MILAN, nil
case "Genoa":
return pb.SevProduct_SEV_PRODUCT_GENOA, nil
default:
return pb.SevProduct_SEV_PRODUCT_UNKNOWN, fmt.Errorf("unknown AMD SEV product: %q", product)
}
}

// ParseProductName returns the KDS project input value, and the model, stepping numbers represented
// by a given V[CL]EK productName extension value, or an error.
func ParseProductName(productName string, key abi.ReportSigner) (*pb.SevProduct, error) {
var product, stepping string
var needStepping bool
switch key {
case abi.VcekReportSigner:
subs := strings.SplitN(productName, "-", 2)
if len(subs) != 2 {
return nil, fmt.Errorf("productName value %q does not match the VCEK expected Name-ModelStepping format", productName)
product, ok := steppingDecoder[productName]
if !ok {
return nil, fmt.Errorf("unknown product name (new stepping published?): %q", productName)
}
product = subs[0]
stepping = subs[1]
needStepping = true
return product, nil
case abi.VlekReportSigner:
// VLEK certificates don't carry the stepping value in productName.
product = productName
}
var name pb.SevProduct_SevProductName
switch product {
case "Milan":
name = pb.SevProduct_SEV_PRODUCT_MILAN
case "Genoa":
name = pb.SevProduct_SEV_PRODUCT_GENOA
default:
return nil, fmt.Errorf("unknown AMD SEV product: %q", product)
}
var modelStepping uint64
if needStepping {
var err error
modelStepping, err = strconv.ParseUint(stepping, 16, 8)
name, err := parseProduct(productName)
if err != nil {
return nil, fmt.Errorf("model stepping in productName is not a hexadecimal byte: %q", stepping)
return nil, err
}
return &pb.SevProduct{Name: name}, nil
}
return &pb.SevProduct{Name: name, ModelStepping: uint32(modelStepping)}, nil
return nil, fmt.Errorf("internal: unhandled reportSigner %v", key)
}

// CrlLinkByKey returns the CRL distribution point for the given key type's
Expand Down
48 changes: 19 additions & 29 deletions kds/kds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,29 +185,29 @@ func TestProductName(t *testing.T) {
}{
{
name: "nil",
want: "Milan-B0",
want: "Milan-B1",
},
{
name: "unknown",
input: &pb.SevProduct{
ModelStepping: 0x1A,
Stepping: 0x1A,
},
want: "Unknown-1A",
want: "badstepping",
},
{
name: "Milan-00",
name: "Milan-B0",
input: &pb.SevProduct{
Name: pb.SevProduct_SEV_PRODUCT_MILAN,
},
want: "Milan-00",
want: "Milan-B0",
},
{
name: "Genoa-FF",
input: &pb.SevProduct{
Name: pb.SevProduct_SEV_PRODUCT_GENOA,
ModelStepping: 0xFF,
Name: pb.SevProduct_SEV_PRODUCT_GENOA,
Stepping: 0xFF,
},
want: "Genoa-FF",
want: "badstepping",
},
}
for _, tc := range tcs {
Expand All @@ -229,36 +229,26 @@ func TestParseProductName(t *testing.T) {
}{
{
name: "empty",
wantErr: "does not match",
},
{
name: "Too much",
input: "Milan-B0-and some extra",
wantErr: "not a hexadecimal byte: \"B0-and some extra\"",
},
{
name: "start-",
input: "-00",
wantErr: "unknown AMD SEV product: \"\"",
},
{
name: "end-",
input: "Milan-",
wantErr: "model stepping in productName is not a hexadecimal byte: \"\"",
wantErr: "unknown product name",
},
{
name: "Too big",
input: "Milan-100",
wantErr: "model stepping in productName is not a hexadecimal byte: \"100\"",
wantErr: "unknown product name",
},
{
name: "happy path",
input: "Genoa-9C",
name: "happy path Genoa",
input: "Genoa-B1",
want: &pb.SevProduct{
Name: pb.SevProduct_SEV_PRODUCT_GENOA,
ModelStepping: 0x9C,
Name: pb.SevProduct_SEV_PRODUCT_GENOA,
Stepping: 1,
},
},
{
name: "bad revision Milan",
input: "Milan-A1",
wantErr: "unknown product name",
},
{
name: "vlek products have no stepping",
input: "Genoa",
Expand Down
2 changes: 1 addition & 1 deletion proto/sevsnp.proto
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ message SevProduct {
}

SevProductName name = 1;
uint32 model_stepping = 2; // Must be a byte
uint32 stepping = 2; // Must be a 4-bit number
}

message Attestation {
Expand Down
57 changes: 28 additions & 29 deletions proto/sevsnp/sevsnp.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions testing/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,8 @@ func (d *Device) Ioctl(command uintptr, req any) (uintptr, error) {
func (d *Device) Product() *spb.SevProduct {
if d.SevProduct == nil {
return &spb.SevProduct{
Name: spb.SevProduct_SEV_PRODUCT_MILAN,
ModelStepping: 0xB0,
Name: spb.SevProduct_SEV_PRODUCT_MILAN,
Stepping: 0, // Milan-B0 is the product name we fake.
}
}
return d.SevProduct
Expand Down
34 changes: 24 additions & 10 deletions verify/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,9 +451,9 @@ func checkProductName(got, want *spb.SevProduct, key abi.ReportSigner) error {
return fmt.Errorf("%v cert product name %v is not %v", key, got, want)
}
// The model stepping number is only part of the VLEK product name, not VLEK's.
if key == abi.VcekReportSigner && got.ModelStepping != want.ModelStepping {
return fmt.Errorf("%v cert product model-stepping number %02X is not %02X",
key, got.ModelStepping, want.ModelStepping)
if key == abi.VcekReportSigner && got.Stepping != want.Stepping {
return fmt.Errorf("%v cert product stepping number %02X is not %02X",
key, got.Stepping, want.Stepping)
}
return nil
}
Expand Down Expand Up @@ -483,6 +483,7 @@ func decodeCerts(chain *spb.CertificateChain, key abi.ReportSigner, options *Opt
if err != nil {
return nil, nil, err
}
fmt.Println("product", exts.ProductName, product)
productName := kds.ProductString(product)
// Ensure the extension product info matches expectations.
if err := checkProductName(product, options.Product, key); err != nil {
Expand Down Expand Up @@ -650,15 +651,13 @@ func SnpAttestation(attestation *spb.Attestation, options *Options) error {
// fillInAttestation uses AMD's KDS to populate any empty certificate field in the attestation's
// certificate chain.
func fillInAttestation(attestation *spb.Attestation, options *Options) error {
var productOverridden bool
if options.Product != nil {
attestation.Product = options.Product
}
if attestation.Product == nil {
// The default product is the first launched SEV-SNP product value.
attestation.Product = &spb.SevProduct{
Name: spb.SevProduct_SEV_PRODUCT_MILAN,
ModelStepping: 0xB0,
}
productOverridden = true
} else if attestation.Product == nil {
attestation.Product = abi.DefaultSevProduct()
productOverridden = true
}
if options.DisableCertFetching {
return nil
Expand Down Expand Up @@ -702,6 +701,21 @@ func fillInAttestation(attestation *spb.Attestation, options *Options) error {
}
}
chain.VcekCert = vcek
if productOverridden {
cert, err := x509.ParseCertificate(vcek)
if err != nil {
return err
}
exts, err := kds.VcekCertificateExtensions(cert)
if err != nil {
return err
}
attestation.Product, err = kds.ParseProductName(exts.ProductName, abi.VcekReportSigner)
fmt.Printf("filled in product with %v\n", attestation.Product)
if err != nil {
return err
}
}
}
case abi.VlekReportSigner:
// We can't lazily ask KDS for the certificate as a user. The CSP must cache their provisioned
Expand Down
Loading

0 comments on commit 93c77e9

Please sign in to comment.