Repository navigation
Fix race condition in db.MemoryStore.RevokeCertificate() - #557
Open
Preston12321 wants to merge 1 commit into
Open
Preston12321 wants to merge 1 commit into
Preston12321 wants to merge 1 commit into
Conversation
pgporada
reviewed
Sep 28, 2026
Comment on lines
+412
to
+414
| // RevokeCertificate atomically marks a certificate as revoked. It returns false | ||
| // without changing anything if the certificate was already revoked, and true | ||
| // if this call revoked it. |
Member
There was a problem hiding this comment.
Suggested change
| // RevokeCertificate atomically marks a certificate as revoked. It returns false | |
| // without changing anything if the certificate was already revoked, and true | |
| // if this call revoked it. | |
| // RevokeCertificate returns false if a certificate was previously revoked, otherwise true. |
| "github.com/letsencrypt/pebble/v2/va" | ||
| ) | ||
|
|
||
| // newTestWFE builds a WebFrontEndImpl backed by a real MemoryStore, CA and VA. |
Member
There was a problem hiding this comment.
Suggested change
| // newTestWFE builds a WebFrontEndImpl backed by a real MemoryStore, CA and VA. | |
| // newTestWFE instantiates a functional WebFrontEndImpl |
aarongable
reviewed
Sep 28, 2026
| // RevokeCertificate atomically marks a certificate as revoked. It returns false | ||
| // without changing anything if the certificate was already revoked, and true | ||
| // if this call revoked it. | ||
| func (m *MemoryStore) RevokeCertificate(cert *core.RevokedCertificate) bool { |
Contributor
There was a problem hiding this comment.
It would be more idiomatic for RevokeCertificate to return an AlreadyRevoked error if the cert is already revoked. Then the WFE can just pass that error through. There are other functions in memorystore.go that already return acme.SomeSpecificProblem, so doing the same here seems good to me.
Comment on lines
3041
to
3044
| revokedCert := wfe.db.GetRevokedCertificateByDER(derBytes) | ||
| if revokedCert != nil { | ||
| return acme.AlreadyRevokedProblem("Certificate has already been revoked.") | ||
| return acme.AlreadyRevokedProblem(alreadyRevokedDetail) | ||
| } |
Contributor
There was a problem hiding this comment.
If both returned errors are identical, there's no need for this early check anymore. Just try to revoke it, and if it's already revoked, return the appropriate error.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two concurrent revocation requests could both succeed, with the latter overwriting the former's
RevokedAtandReason. This isn't much of an issue today, but will cause non-compliant behavior when CRLs are implemented per #531.The issue is that revocation status is never checked in the same guard as it is written. Here's the relevant section of
wfe.WebFrontEndImpl.processRevocation():The
GetRevokedCertificateByDER()method uses a mutex read lock and checks whether the given certificate is already considered revoked. ThenRevokeCertificate()uses a mutex write lock and immediately writes an entry to the internalrevokedCertificatesByIDmap.Because those two operations happen in different critical sections, it's possible for two goroutines to sync up right before the
RevokeCertificate()call and result in two atomic writes to the same map entry, effectively erasing the first write.This change fixes the race by having
RevokeCertificate()check the map first and return whether it made any change. Now, the first write will succeed while all subsequent writes result in anAlreadyRevokedProblem.Note: I used Claude to discover this issue and work up the changeset. I've reviewed it myself and made stylistic edits to code structure and comments before opening the PR. This description is my own writing 🙂