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

Fix flaky CT submission bug #1085

Merged
merged 19 commits into from
Jun 23, 2023
Merged
Show file tree
Hide file tree
Changes from 16 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## HEAD

### Fix flaky submission failures
* #1084: CT flaky submission failures

### Add support for WASI port
* Add build tags for wasip1 GOOS

Expand Down
6 changes: 3 additions & 3 deletions ctpolicy/chromepolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import (
)

const (
minOperators = 2 // minimum number of distinct CT log operators that issue an SCT.
dayDuration = 86400 * time.Second // time.Duration of one day
dayDuration = 24 * time.Hour // time.Duration of one day
minDistinctOperators = 2 // Number of distinct CT log operators that submit an SCT
)

// ChromeCTPolicy implements logic for complying with Chrome's CT log policy
Expand Down Expand Up @@ -56,7 +56,7 @@ func (chromeP ChromeCTPolicy) LogsByGroup(cert *x509.Certificate, approved *logl
if err != nil {
return nil, err
}
baseGroup.MinOperators = minOperators
baseGroup.MinDistinctOperators = minDistinctOperators
groups[baseGroup.Name] = baseGroup
return groups, nil
}
Expand Down
6 changes: 3 additions & 3 deletions ctpolicy/chromepolicy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ func wantedGroups(base int, minusBob bool) LogPolicyData {
"https://ct.googleapis.com/racketeer/": true,
"https://log.bob.io": true,
},
MinInclusions: base,
MinOperators: minOperators,
IsBase: true,
MinInclusions: base,
MinDistinctOperators: minDistinctOperators,
IsBase: true,
LogWeights: map[string]float32{
"https://ct.googleapis.com/logs/argon2020/": 1.0,
"https://ct.googleapis.com/aviator/": 1.0,
Expand Down
14 changes: 7 additions & 7 deletions ctpolicy/ctpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ const (

// LogGroupInfo holds information on a single group of logs specified by Policy.
type LogGroupInfo struct {
Name string
LogURLs map[string]bool // set of members
MinInclusions int // Required number of submissions.
MinOperators int // Required number of distinct CT log operators.
IsBase bool // True only for Log-group covering all logs.
LogWeights map[string]float32 // weights used for submission, default weight is 1
wMu sync.RWMutex // guards weights
Name string
LogURLs map[string]bool // set of members
MinInclusions int // Required number of submissions.
MinDistinctOperators int // Required number of distinct CT log operators that submit an SCT.
IsBase bool // True only for Log-group covering all logs.
LogWeights map[string]float32 // weights used for submission, default weight is 1
wMu sync.RWMutex // guards weights
}

func (group *LogGroupInfo) setMinInclusions(i int) error {
Expand Down
71 changes: 23 additions & 48 deletions submission/races.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,27 +52,24 @@ type groupState struct {
// When some group is complete cancels all requests that are not needed by any
// group.
type safeSubmissionState struct {
mu sync.Mutex
logToGroups map[string]ctpolicy.GroupSet
groupNeeds map[string]int // number of logs that need to be submitted for each group.
minGroups int // minimum number of distinct groups that need a log submitted.
mu sync.Mutex
logToGroups map[string]ctpolicy.GroupSet
remainingSubmissions int // number of logs that still need to be submitted.
minDistinctGroups int // number of groups that need a submission
groupsSubmitted map[string]int // number of logs submitted to each group.

groups map[string]bool // set of groups that have a log submitted.
results map[string]*submissionResult
cancels map[string]context.CancelFunc
}

func newSafeSubmissionState(groups ctpolicy.LogPolicyData) *safeSubmissionState {
var s safeSubmissionState
s.logToGroups = ctpolicy.GroupByLogs(groups)
s.groupNeeds = make(map[string]int)
for _, g := range groups {
s.groupNeeds[g.Name] = g.MinInclusions
}
if baseGroup, ok := groups[ctpolicy.BaseName]; ok {
s.minGroups = baseGroup.MinOperators
s.remainingSubmissions = baseGroup.MinInclusions
s.minDistinctGroups = baseGroup.MinDistinctOperators
}
s.groups = make(map[string]bool)
s.groupsSubmitted = make(map[string]int)
s.results = make(map[string]*submissionResult)
s.cancels = make(map[string]context.CancelFunc)
return &s
Expand All @@ -88,15 +85,7 @@ func (sub *safeSubmissionState) request(logURL string, cancel context.CancelFunc
return false
}
sub.results[logURL] = &submissionResult{}
isAwaited := false
for g := range sub.logToGroups[logURL] {
if sub.groupNeeds[g] > 0 {
isAwaited = true
break
}
}
if !isAwaited {
// No groups expecting result from this Log.
if sub.remainingSubmissions <= 0 {
return false
}
sub.cancels[logURL] = cancel
Expand All @@ -113,51 +102,41 @@ func (sub *safeSubmissionState) setResult(logURL string, sct *ct.SignedCertifica
sub.results[logURL] = &submissionResult{sct: sct, err: err}
return
}
// group name associated with logURL outside of BaseName.
// (this assumes the logURL is associated with only one group ignoring BaseName)
// If at least one group needs that SCT, result is set. Otherwise dumped.
for groupName := range sub.logToGroups[logURL] {
// Ignore the base group (All-logs) here to check separately.
if groupName == ctpolicy.BaseName {
continue
}
if sub.groupNeeds[groupName] > 0 {
// Set the result if the group does not have a submission.
if sub.groupsSubmitted[groupName] == 0 {
sub.results[logURL] = &submissionResult{sct: sct, err: err}
sub.groupsSubmitted[groupName]++
roger2hk marked this conversation as resolved.
Show resolved Hide resolved
}
sub.groups[groupName] = true
sub.groupNeeds[groupName]--
}

// Check the base group (All-logs) only
if sub.logToGroups[logURL][ctpolicy.BaseName] {
if sub.results[logURL].sct != nil {
// It is already processed in a non-base group, so we can reduce the groupNeeds for the base group as well.
roger2hk marked this conversation as resolved.
Show resolved Hide resolved
sub.groupNeeds[ctpolicy.BaseName]--
} else if sub.groupNeeds[ctpolicy.BaseName] > 0 {
minInclusionsForOtherGroup := 0
for g, cnt := range sub.groupNeeds {
if g != ctpolicy.BaseName && cnt > 0 {
minInclusionsForOtherGroup += cnt
}
}
sub.remainingSubmissions--
} else if sub.remainingSubmissions > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch is only executed if sub.results[logURL].sct == nil, i.e if the SCT was not matched to a groupName above. Is this even something that we want to allow?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sub.results[logURL].sct == nil means a previous SCT has been set in sub.results from the same log operator (the sct gets set to nil in sub.request(...)). This branch is used for the situation where an SCT from an already submitted log operator can be used in the results.

extraSubmissions := sub.minDistinctGroups - len(sub.groupsSubmitted)
// Set the result only if the base group still needs SCTs more than total counts
// of minimum inclusions for other groups.
if sub.groupNeeds[ctpolicy.BaseName] > minInclusionsForOtherGroup {
if sub.remainingSubmissions > extraSubmissions {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this just be: extraSubmissions > 0?
Why does it compare a number of SCTs, with a number of groups?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extraSubmissions represents the number of SCTs that need to be submitted to other log operators. If we are in this part of the branch, it implies that an SCT has already been submitted under this log operator. We need to make sure that adding this SCT to sub.results will still allow us to satisfy the distinct log operator criterion. For example, a certificate that has a lifetime > 180 days requires 3 SCTs but only 2 distinct log operators. This means we can use 2 SCTs from 1 log operator. We need to compare extraSubmissions with sub.remainingSubmissions

extraSubmissions was a misleading name. I have renamed it to reservedSubmissions and added a comment to clear up the confusion.

sub.results[logURL] = &submissionResult{sct: sct, err: err}
sub.groupNeeds[ctpolicy.BaseName]--
sub.remainingSubmissions--
}
}
}

// Cancel any pending Log-requests for which there're no more awaiting
// Log-groups.
for logURL, groupSet := range sub.logToGroups {
isAwaited := false
for g := range groupSet {
if sub.groupNeeds[g] > 0 {
isAwaited = true
break
}
}
if !isAwaited && sub.cancels[logURL] != nil {
for logURL := range sub.logToGroups {
if sub.remainingSubmissions <= 0 && sub.cancels[logURL] != nil {
sub.cancels[logURL]()
sub.cancels[logURL] = nil
}
Expand All @@ -168,14 +147,10 @@ func (sub *safeSubmissionState) setResult(logURL string, sct *ct.SignedCertifica
func (sub *safeSubmissionState) groupComplete(groupName string) bool {
roger2hk marked this conversation as resolved.
Show resolved Hide resolved
sub.mu.Lock()
defer sub.mu.Unlock()
needs, ok := sub.groupNeeds[groupName]
if !ok {
return true
}
if len(sub.groups) < sub.minGroups {
if len(sub.groupsSubmitted) < sub.minDistinctGroups {
return false
}
return needs <= 0
return sub.remainingSubmissions <= 0
}

func (sub *safeSubmissionState) collectSCTs() []*AssignedSCT {
Expand Down
34 changes: 17 additions & 17 deletions submission/races_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func TestGetSCTs(t *testing.T) {
name: "singleGroupOneSCT",
sbMock: &mockSubmitter{fixedDelay: map[byte]time.Duration{'a': 0}, firstLetterURLReqNumber: make(map[byte]int)},
groups: ctpolicy.LogPolicyData{
"a": {
ctpolicy.BaseName: {
Name: "a",
LogURLs: map[string]bool{"a1.com": true, "a2.com": true},
MinInclusions: 1,
Expand All @@ -107,7 +107,7 @@ func TestGetSCTs(t *testing.T) {
name: "singleGroupMultiSCT",
sbMock: &mockSubmitter{fixedDelay: map[byte]time.Duration{'a': 0}, firstLetterURLReqNumber: make(map[byte]int)},
groups: ctpolicy.LogPolicyData{
"a": {
ctpolicy.BaseName: {
Name: "a",
LogURLs: map[string]bool{"a1.com": true, "a2.com": true, "a3.com": true, "a4.com": true, "a5.com": true},
MinInclusions: 3,
Expand All @@ -124,27 +124,27 @@ func TestGetSCTs(t *testing.T) {
"a": {
Name: "a",
LogURLs: map[string]bool{"a1.com": true, "a2.com": true, "a3.com": true, "a4.com": true},
MinInclusions: 1,
MinInclusions: 0,
IsBase: false,
LogWeights: map[string]float32{"a1.com": 1.0, "a2.com": 1.0, "a3.com": 1.0, "a4.com": 1.0},
},
"b": {
Name: "b",
LogURLs: map[string]bool{"b1.com": true, "b2.com": true, "b3.com": true, "b4.com": true},
MinInclusions: 1,
MinInclusions: 0,
IsBase: false,
LogWeights: map[string]float32{"b1.com": 1.0, "b2.com": 1.0, "b3.com": 1.0, "b4.com": 1.0},
},
ctpolicy.BaseName: {
Name: ctpolicy.BaseName,
LogURLs: map[string]bool{"a1.com": true, "a2.com": true, "a3.com": true, "a4.com": true, "b1.com": true, "b2.com": true, "b3.com": true, "b4.com": true},
MinInclusions: 5,
MinOperators: 2,
IsBase: true,
LogWeights: map[string]float32{"a1.com": 1.0, "a2.com": 1.0, "a3.com": 1.0, "a4.com": 1.0, "b1.com": 1.0, "b2.com": 1.0, "b3.com": 1.0, "b4.com": 1.0},
Name: ctpolicy.BaseName,
LogURLs: map[string]bool{"a1.com": true, "a2.com": true, "a3.com": true, "a4.com": true, "b1.com": true, "b2.com": true, "b3.com": true, "b4.com": true},
MinInclusions: 2,
MinDistinctOperators: 2,
IsBase: true,
LogWeights: map[string]float32{"a1.com": 1.0, "a2.com": 1.0, "a3.com": 1.0, "a4.com": 1.0, "b1.com": 1.0, "b2.com": 1.0, "b3.com": 1.0, "b4.com": 1.0},
},
},
resultTrail: map[string]int{"a": 1, "b": 1, ctpolicy.BaseName: 5},
resultTrail: map[string]int{"a": 1, "b": 1, ctpolicy.BaseName: 2},
},
{
name: "notEnoughDistinctOperators",
Expand All @@ -158,12 +158,12 @@ func TestGetSCTs(t *testing.T) {
LogWeights: map[string]float32{"a1.com": 1.0, "a2.com": 1.0, "a3.com": 1.0, "a4.com": 1.0},
},
ctpolicy.BaseName: {
Name: ctpolicy.BaseName,
LogURLs: map[string]bool{"a1.com": true, "a2.com": true, "a3.com": true, "a4.com": true},
MinInclusions: 2,
MinOperators: 2,
IsBase: true,
LogWeights: map[string]float32{"a1.com": 1.0, "a2.com": 1.0, "a3.com": 1.0, "a4.com": 1.0},
Name: ctpolicy.BaseName,
LogURLs: map[string]bool{"a1.com": true, "a2.com": true, "a3.com": true, "a4.com": true},
MinInclusions: 2,
MinDistinctOperators: 2,
IsBase: true,
LogWeights: map[string]float32{"a1.com": 1.0, "a2.com": 1.0, "a3.com": 1.0, "a4.com": 1.0},
},
},
errRegexp: regexp.MustCompile("didn't receive enough SCTs"),
Expand Down