Improve etcd-based RW locks and API contextual awarness of cancelation - #1180
Merged
Conversation
… keepalive to avoid idling
…licating the call (race condition)
|
The latest Buf updates on your PR. Results from workflow CI / buf-lint (pull_request).
|
Pull Request Test Coverage Report for Build 20935595152Details
💛 - Coveralls |
NicoFgrx
reviewed
Jan 12, 2026
NicoFgrx
reviewed
Jan 12, 2026
NicoFgrx
left a comment
Member
There was a problem hiding this comment.
the excalidraw is not renderer properly
NicoFgrx
approved these changes
Jan 12, 2026
mcbloch
pushed a commit
to mcbloch/chall-manager
that referenced
this pull request
Feb 6, 2026
ctfer-io#1180) * impr(locks): handle context cancelation with recovery mecanisms * impr(locks): don't return cancelation errors * impr(locks): improve cancelation and error handling with session-awareness * fix(locks): typing and local lock context cancelation * impr(locks): add etcd healthcheck time windows to avoid spamming, add keepalive to avoid idling * impr(api): handle context cancelation for better recovery, fix bugs * fix(etcd): protect the whole healthcheck for time window to avoid duplicating the call (race condition) * docs: add recovery of RWLocks in the design webdoc * fix(api): unprotected segment in delete operation leading to potential race conditions * docs: fix figure and clarify CP-/AP-first decision on using etcd
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.
What is the problem/feature ?
Throughout many internal usage at CTFer.io, we often deployed single instances of Chall-Manager.
Given this context, we often relied on a local lock. This is also the default setting, and is not documented else way.
Nonetheless, it is mandatory for production purposes to use etcd at it needs scaling and recoverability in case of a transient failure.
For instance, #1074 shows people are actually using the etcd-based RW locks 👍
Even though there seem to be no major issues with the triple RW lock chain, this mostly hold to nothing as many assumptions were maid. One of them is that a request cannot be canceled, which is not realistic (e.g. a upstream service with a timeout)...
In such cases, we often observed recovery after a while, but was not thanks to automated recovery in Chall-Manager... It has been due to etcd session closing, revoking the locks, and by luck did not break the RW locks' counters.
So, the problem is that etcd-based RW locks do not handle context cancelation, nor does the API.
What contains this PR ?
This PR is quite heavy, as it improves drastically the contextual awarness of the business layer (aka the
/apidirectory) and the etcd RW locks implementation.Sadly, it contains too much to simply write a meaningfull TL;DR... but it improves etcd-based RW locks A LOT.
Time-attack bugs
I discovered many ways of soft-locking etcd-based RW locks based upon time-attacks context cancelations.
It is difficult to actually exploit in production systems reliably due to networking delays and pseudo-random assumptions on RW locks parallelization timings, so I don't consider exploitation possible, tho we might discuss it more thorously later. An attacker might spam all endpoints with random timeouts until something happen, at best...
Through careful review, I especially discovered 3 bugs:
The first is easily recoverable and won't cause a lot of trouble (I think, or guess).
The second one is recoverable but can lead to strange transient errors, hindering atomicity of API operations thus predictability.
The last would cause trouble, as it would be possible to soft-lock Chall-Manager, but it is out of upstream services' scope so I don't consider it a vulnerability.
Etcd
For each lock created as part of an API request, there was a set of sessions opened and later closed through response (between 1 -for the TOTW- and m+n+1 with m the number of challenges and n the total number of instances).
While this worked, it spammed etcd with many useless work.
Moreover, in case a session is terminated, the locks where automatically freed thus future unlocks would end up erroring (i.e., already unlocked).
For these reasons, I move the session management to the global etcd service management and hide underlying complexities of renewing it.
When locks where used, the context was used blindly, but might have been canceled by the upstream service (e.g., timeout).
This might have led to un-performed steps in the recovery mecanisms of the current implementation...
With this PR, they now consider the context to be cancellable and deal with every situation it might have been canceled such that it recovers and maintain a transation-like behavior.
For healthchecks, current implementation performs a get on
healthkey in etcd, sometimes multiple times for each usage of the etcd service!This spams etcd A LOT for no specific reason: we can deal with the assumption that it was recently fine, thus skip checking again. Actually, I arbitrarly estimated "fine" to be 10 seconds.
That way, we reduce the load on etcd and improve performances.
Finally, we sometime observed long-running requests around 40 seconds... But never in production 🤔
After analysis, it might have been due to no keepalive mecanism used on the etcd client, thus once the system is idle for too long (i.e., more than 5 minutes in the default settings) it hits all timeouts and need to recreate every connection...
For this reason, I activate the keepalive mecanism of the etcd client in the etcd service manager, and confirm that after 5 minutes the first request is not a long-running one.
In production, this was not a problem as the janitor issued at least 1 API call every minute, so way below deadlines.
Oh, not to forget that I clarify the current assumptions for etcd-based locks: unqueued/unfair locks, rely on a stable network connection.
Overall, the use of etcd I implemented months ago sucked 🙃
API
In the "business" layer of Chall-Manager, the context cancelation is now a handled case, similarly to the etcd locks.
Nonetheless, it showed room for improvements:
Some observations
What are the impacts ?
The etcd-based setup for RW locks do not longer spams error logs when running, and recover way better in case of request cancelation.
Resolves #1074