-
Notifications
You must be signed in to change notification settings - Fork 1
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
The idea of CertPolicyBase::CertServer is vulnerable to race conditions #2
Comments
I deliberately provided
I did test this module under some load, but didn't encounter race conditions. Maybe my tests weren't sufficient. But anyways, even in current implementation it is relatively easy to address race condition issue by using locks:
Of course, this will slightly slow things down, because multiple threads will stay in queue while awaiting lock release and execute one-by-one (synchronously). I think that this approach should reduce race condition chances. What do you think? |
But, according to your proposal, as a developer, I am now placed between two sacrificial choices:
For sure, ignoring errors is not my choice.
Thus, we hit the "chicken and egg" problem:
From the considerations described previously, I see two consequences:
|
Look, Microsoft CA's throughput is much higher than your policy module's throughput, because CA is COM server (native code), your policy module is .NET runtime (just wrapped in CCW). .NET runtime is always slower than native code, this is not debatable. Not to mention that there is additional overhead in marshalling data between COM and .NET runtime (in both directions), which slows things even more. So the answer to your question:
is "no", unfortunately and it is not because how I designed this framework. Under high load, .NET runtime is a bottleneck and you may run out of memory if high load is sustained for reasonable period, because CA will make calls faster than your ability to process. This is a classic problem. If you are looking for a policy module which performance would be comparable to CA performance, you would write it using native code (C++). Then you can have fully multi-threaded and thread safe policy/exit module. .NET is a RAD platform that allows you to implement your arbitrary logic quickly. However this convenience comes at cost: performance. I put all reasonable efforts to make the code as performant as possible, by keeping the balance with maintainability and easiness/usefulness. When I hit such performance problem, I prefer to slow things down rather than allowing uncontrolled parallel thread creation and crashing entire server when it runs out of memory.
thread synchronization turns your code (in critical sections) into single-threaded code by definition. It doesn't matter much how exactly you implement this synchronization. Though, it may be possible to make some kind of controlled thread pool. Say, have 100 threads (with singleton CertServer instance in each) in the pool and allow to run up to 100 threads in parallel (each thread will have its own context) and block new thread creation when pool is exhausted until some threads are released. This should boost throughput to some extent. The question then, what is the "optimal" number of parallel threads and how to test this. As you may already noticed, it is not easy to make policy module testing (both, logic and performance).
You are not required to use protected |
That is the first thing I have done, after I hit errors on ADCS due to the problem of race-condition. public class MyPolicyModule : CertPolicyBase {
readonly Object _myLock = new();
// the rest is omitted for brevity
public override PolicyModuleAction VerifyRequest(String strConfig, Int32 Context, Int32 bNewRequest, Int32 Flags) {
lock (_myLock) {
PolicyModuleAction nativeResult = base.VerifyRequest(strConfig, Context, bNewRequest, Flags);
// base.VerifyRequest contains: "CertServer.InitializeContext(Context);" which is NOT thread-safe
CertServer.FinalizeContext();
}
// ...
// my logic goes here, and it is thread-safe
// ...
}
// the rest is omitted for brevity
} Currently I see no way to call microsoft's native policy module using ADCS-CertMod framework without using locks. |
Directly calling |
I have no plans to expose this func to inheritors, this is to ensure that this method is not misused. In addition, I want to ensure that this func is always called before running your own code. |
Can a new interface be introduced into the For example: public PolicyModuleAction VerifyRequest2(String strConfig, Int32 Context, Int32 bNewRequest, Int32 Flags) {
return funcVerifyRequest.Invoke(strConfig, Context, bNewRequest, Flags);
} Thus a developer will be able to call public class MyPolicyModule : CertPolicyBase {
// readonly Object _myLock = new(); // NOT NEEDED any more
// the rest is omitted for brevity
public override PolicyModuleAction VerifyRequest(String strConfig, Int32 Context, Int32 bNewRequest, Int32 Flags) {
// lock (_myLock) { // NOT NEEDED any more
PolicyModuleAction nativeResult = base.VerifyRequest2(strConfig, Context, bNewRequest, Flags);
// the call is thread-safe, because thread-unsafe `CertServer` is not used
// CertServer.FinalizeContext();
// }
// ...
// my logic goes here, and it is thread-safe
// ...
}
// the rest is omitted for brevity
} |
I have made the draft PR for my last proposition. |
@phonexicum sorry for late response. I've thought about a couple of solutions for this issue and still the best one is the approach provided in this comment. Here is a PR that implements this: #7 I'm still not convinced that exposing private delegate ( Please let me know what you think about this implementation. p.s. I acknowledge that your use case doesn't necessary require |
Hi, first of all, thank you for this project, it is awesome.
problem description
During development of policy module for the ADCS, I run into a problem of race conditions.
It turns out the ADCS (certsrv.exe) service under high load may execute
ICertPolicy::VerifyRequest
concurrently in different threads.And if one will use in VerifyRequest some shared resources (for example
CertPolicyBase.CertServer
), it may result in unpredictable behavior.Examples of errors could be:
Eventually errors result in a broken ADCS service, and manual service restart was required.
The problem could be simply solved: I just didn't used
CertPolicyBase.CertServer
provided by abstact class and instead created and initialized new instance ofCertServerModule
in everyVerifyRequest
function call, without any sharing between threads.problem interpretation
For a reason described above, current approach (of providing access to the property
CertPolicyBase.CertServer
for a developer) looks error prone.I believe, that the project ADCS-SID-Extension-Policy-Module has the same problems with race conditions, which will only reveal itself under high load, because the
CertServer
is used in a couple of places atPolicy.cs
file.I believe, additionally race conditions may result in:
CertServerModule::FinalizeContext()
(e.g. at policy module)CertServer.InitializeContext(Context);
(e.g. at base.VerifyRequest) in case when two threads initialize CertServer, but only memory allocated by second thread will be freed later.(unfortunately, this
InitializeContext
seems to be unavoidable if the developer wish to use for example microsoft's native policy module)proposition
I propose to completely remove
CertServer
property from abstract classCertPolicyBase
. If the developer requires the object to read or edit properties of a certificate, he must instantiate new object in each VerifyRequest thread.The text was updated successfully, but these errors were encountered: