From f62c3ed2c8313bc5b9006d69d173a3a9aa6486a1 Mon Sep 17 00:00:00 2001 From: Alex Wu Date: Wed, 15 Mar 2023 15:47:16 -0700 Subject: [PATCH] test: Handle Arch Linux eventlog PCR4 failures The Arch Linux event log does not extend the "Calling EFI Application" event before extending EFI Application digests. Update tests to handle (aka, ignore) this bad event log. --- server/eventlog_test.go | 32 ++++++++------ server/grouped_error.go | 29 +++++++++++++ server/grouped_error_test.go | 84 ++++++++++++++++++++++++++++++++++++ server/policy_test.go | 16 +++---- 4 files changed, 139 insertions(+), 22 deletions(-) diff --git a/server/eventlog_test.go b/server/eventlog_test.go index 5abb06937..649f5b9fa 100644 --- a/server/eventlog_test.go +++ b/server/eventlog_test.go @@ -25,7 +25,11 @@ type eventLog struct { ExpectedEFIAppDigests map[pb.HashAlgo][]string } -var archLinuxBadSecureBoot = "SecureBoot data len is 0, expected 1" +// The Arch Linux event log has two known failures due to our parser's strict checks. +var archLinuxKnownParsingFailures = []string{ + "SecureBoot data len is 0, expected 1", + "found EFIBootServicesApplication in PCR4 before CallingEFIApp event", +} // Agile Event Log from a RHEL 8 GCE instance with Secure Boot enabled var Rhel8GCE = eventLog{ @@ -483,21 +487,21 @@ func TestParseEventLogs(t *testing.T) { Bootloader // This field handles known issues with event log parsing or bad event // logs. - // An empty string will not attempt to pattern match the error result. - errorSubstr string + // Set to nil when the event log has no known issues. + errorSubstrs []string }{ - {Debian10GCE, "Debian10GCE", UnsupportedLoader, ""}, - {Rhel8GCE, "Rhel8GCE", GRUB, ""}, - {UbuntuAmdSevGCE, "UbuntuAmdSevGCE", GRUB, ""}, + {Debian10GCE, "Debian10GCE", UnsupportedLoader, nil}, + {Rhel8GCE, "Rhel8GCE", GRUB, nil}, + {UbuntuAmdSevGCE, "UbuntuAmdSevGCE", GRUB, nil}, // TODO: remove once the fix is pulled in // https://github.com/google/go-attestation/pull/222 - {Ubuntu2104NoDbxGCE, "Ubuntu2104NoDbxGCE", GRUB, sbatErrorStr}, - {Ubuntu2104NoSecureBootGCE, "Ubuntu2104NoSecureBootGCE", GRUB, sbatErrorStr}, + {Ubuntu2104NoDbxGCE, "Ubuntu2104NoDbxGCE", GRUB, []string{sbatErrorStr}}, + {Ubuntu2104NoSecureBootGCE, "Ubuntu2104NoSecureBootGCE", GRUB, []string{sbatErrorStr}}, // This event log has a SecureBoot variable length of 0. - {ArchLinuxWorkstation, "ArchLinuxWorkstation", UnsupportedLoader, archLinuxBadSecureBoot}, - {COS85AmdSev, "COS85AmdSev", GRUB, ""}, - {COS93AmdSev, "COS93AmdSev", GRUB, ""}, - {COS101AmdSev, "COS101AmdSev", GRUB, ""}, + {ArchLinuxWorkstation, "ArchLinuxWorkstation", UnsupportedLoader, archLinuxKnownParsingFailures}, + {COS85AmdSev, "COS85AmdSev", GRUB, nil}, + {COS93AmdSev, "COS93AmdSev", GRUB, nil}, + {COS101AmdSev, "COS101AmdSev", GRUB, nil}, } for _, log := range logs { @@ -511,10 +515,10 @@ func TestParseEventLogs(t *testing.T) { if !ok { t.Errorf("ParseMachineState should return a GroupedError") } - if log.errorSubstr == "" { + if len(log.errorSubstrs) == 0 { t.Errorf("expected no errors in GroupedError, received (%v)", err) } - if !gErr.containsOnlySubstring(log.errorSubstr) { + if !gErr.containsKnownSubstrings(log.errorSubstrs) { t.Errorf("failed to parse and replay log: %v", err) } } diff --git a/server/grouped_error.go b/server/grouped_error.go index e6ca0c7a1..26a4f5a06 100644 --- a/server/grouped_error.go +++ b/server/grouped_error.go @@ -40,6 +40,35 @@ func (gErr *GroupedError) containsSubstring(substr string) bool { return false } +// containsKnownSubstrings is used to match a set of known errors. +// Each substring must only match error in the GroupedError. +// In other words, there must not be overlap in the substring matches. +func (gErr *GroupedError) containsKnownSubstrings(substrs []string) bool { + if len(gErr.Errors) != len(substrs) { + return false + } + matchedGErr := make(map[string]bool) + for _, err := range gErr.Errors { + matchedGErr[err.Error()] = false + for _, substr := range substrs { + if strings.Contains(err.Error(), substr) { + if matchedGErr[err.Error()] { + // Duplicated match for the error. + return false + } + matchedGErr[err.Error()] = true + } + } + } + + for _, matched := range matchedGErr { + if !matched { + return false + } + } + return true +} + func (gErr *GroupedError) containsOnlySubstring(substr string) bool { if len(gErr.Errors) != 1 { return false diff --git a/server/grouped_error_test.go b/server/grouped_error_test.go index 50733f489..f2600c9b0 100644 --- a/server/grouped_error_test.go +++ b/server/grouped_error_test.go @@ -40,3 +40,87 @@ func TestCreateGroupedErrorFail(t *testing.T) { t.Errorf("expected nil error!") } } + +func TestContainsOnlySubstring(t *testing.T) { + wholeString := "err error errorz" + err := errors.New(wholeString) + outErr := GroupedError{Prefix: "foo:", Errors: []error{err}} + if !outErr.containsOnlySubstring("error") { + t.Errorf("expected a match for substring") + } + if !outErr.containsOnlySubstring("err") { + t.Errorf("expected a match for substring") + } + if !outErr.containsOnlySubstring("") { + t.Errorf("expected a match for substring") + } + if !outErr.containsOnlySubstring(wholeString) { + t.Errorf("expected a match for substring") + } +} + +func TestContainsOnlySubstringsFalse(t *testing.T) { + wholeString := "err error errorz" + err := errors.New(wholeString) + outErr := GroupedError{Prefix: "foo:", Errors: []error{err}} + + tests := []struct { + name string + substring string + }{ + {"AdditionalCharacterStart", "." + wholeString}, + {"AdditionalCharacterEnd", wholeString + "."}, + {"RemovedCharacter", wholeString[:5] + wholeString[6:]}, + {"ReplacedCharacter", wholeString[:5] + "." + wholeString[6:]}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if outErr.containsOnlySubstring(test.substring) { + t.Errorf("expected failed matching for substring") + } + + }) + } +} + +func TestContainsKnownSubstrings(t *testing.T) { + err := errors.New("err error errorz") + err2 := errors.New("new newww newzz") + err3 := errors.New("iss issue issues") + outErr := GroupedError{Prefix: "foo:", Errors: []error{err, err2, err3}} + if !outErr.containsKnownSubstrings([]string{"error", " newzz", " issue "}) { + t.Errorf("expected a match for known substrings") + } +} + +func TestContainsKnownSubstringsFalse(t *testing.T) { + err := errors.New("err error errorz") + err2 := errors.New("new newww newzz") + err3 := errors.New("iss issue issues") + outErr := GroupedError{Prefix: "foo:", Errors: []error{err, err2, err3}} + + tests := []struct { + name string + substrings []string + }{ + {"NoSubstrings", []string{}}, + {"OneEmptySubstring", []string{""}}, + // Should fail, since there is overlap between substrings. + {"AllEmptySubstrings", []string{"", "", ""}}, + {"FewerSubstrings", []string{"err"}}, + {"FewerSubstrings2", []string{"error", " issue "}}, + {"MoreSubstrings", []string{"error", " newzz", " issue ", " issues"}}, + {"MoreSubstrings5", []string{"error", " newzz", " issue ", " issues", "err"}}, + {"OverlappingSubstrings", []string{"error", " err", " issue "}}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if outErr.containsKnownSubstrings(test.substrings) { + t.Errorf("expected failed matching for known substrings") + } + + }) + } +} diff --git a/server/policy_test.go b/server/policy_test.go index eda38cf68..fbfd0d590 100644 --- a/server/policy_test.go +++ b/server/policy_test.go @@ -86,7 +86,7 @@ func TestEvaluatePolicySCRTM(t *testing.T) { machineState, err := parsePCClientEventLog(ArchLinuxWorkstation.RawLog, ArchLinuxWorkstation.Banks[0], UnsupportedLoader) if err != nil { gErr := err.(*GroupedError) - if !gErr.containsOnlySubstring(archLinuxBadSecureBoot) { + if !gErr.containsKnownSubstrings(archLinuxKnownParsingFailures) { t.Fatalf("failed to get machine state: %v", err) } } @@ -125,15 +125,15 @@ func TestEvaluatePolicyFailure(t *testing.T) { policy *pb.Policy // This field handles known issues with event log parsing or bad event // logs. - // An empty string will not attempt to pattern match the error result. - errorSubstr string + // Set to nil when the event log has no known issues. + errorSubstrs []string }{ - {"Debian10-SHA1", Debian10GCE, &badGcePolicyVersion, ""}, - {"Debian10-SHA1", Debian10GCE, &badGcePolicySEV, ""}, + {"Debian10-SHA1", Debian10GCE, &badGcePolicyVersion, nil}, + {"Debian10-SHA1", Debian10GCE, &badGcePolicySEV, nil}, {"Ubuntu1804AmdSev-CryptoAgile", UbuntuAmdSevGCE, &badGcePolicySEVES, - ""}, + nil}, {"ArchLinuxWorkstation-CryptoAgile", ArchLinuxWorkstation, - &badPhysicalPolicy, archLinuxBadSecureBoot}, + &badPhysicalPolicy, archLinuxKnownParsingFailures}, } for _, test := range tests { @@ -141,7 +141,7 @@ func TestEvaluatePolicyFailure(t *testing.T) { machineState, err := parsePCClientEventLog(test.log.RawLog, test.log.Banks[0], UnsupportedLoader) if err != nil { gErr := err.(*GroupedError) - if test.errorSubstr != "" && !gErr.containsOnlySubstring(test.errorSubstr) { + if len(test.errorSubstrs) == 0 || !gErr.containsKnownSubstrings(test.errorSubstrs) { t.Fatalf("failed to get machine state: %v", err) } }