-
Notifications
You must be signed in to change notification settings - Fork 298
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
Possible thread safety issue with RequestValidator.cs causing System.Security.Cryptography.CryptographicException #466
Comments
Hello @seannybgoode, Thanks for taking the time to report this! This issue has been added to our internal backlog to be prioritized. Pull requests and +1s on the issue summary will help it move up the backlog. With best regards, Elmer |
Here are my thoughts on a fix here: The object disposed exceptions are most likely occurring when a validation flow is in-progress, an exception occurs, a re-init is triggered, and theres some garbage collection with causes the _hmac._disposed flag to flip to true. This is a bit of a red-herring though, I think these requests would fail anyway, as they're working on an the member that needed to be reinitialized anyway. From what I can tell, the root cause here is that two threads could call ComputeHash on the same instance at the same time. This corrupts the members of the object irrecoverably, and causes all subsequent requests to fail. Options for a fix:
|
What about option 1 and making the |
@childish-sambino - I had considered that. However making an object thread local means that it will be initialized by reflection, which is expensive. Not knowing the cost, compared to the other options, I didn't mention it. I'd need to put some time in to see if the benefit outweighs the cost. I just tested 3, and it doesn't seem to cause any noticeable issues with our services on my development branch. The more I think about it, the more I like it, as it shortcuts any other unknown threading issues (although the SHA256 member seems unaffected, to my knowledge). Failing that, we could try 2 like so:
As far as I understand, C# can handle locking on any reference variable by that method. As you said before though, we should test that somehow. |
Spent some time wrapping my head around action filters and threading. This helped: https://stackoverflow.com/a/8937793 I'm in favor of making the change in the
or this:
|
@childish-sambino - The latter is exactly what I did in our systems. In the
Maybe we can kill two birds, if option 3 is the approach? Would like to help out the community by improving the documentation, if we go with option 3. |
The protocol could be related to what's proxying the requests. There was a similar issue in the Node helper lib where they were running an HTTP server but using an HTTPS heroku endpoint. The Twilio server-side signature calculation is based on HTTPS since that's what the webhook URL says, but it was being forwarded to the app by the proxy with HTTP as the protocol. The Node request validator allows you to pass the expected protocol as an arg which will be used to do the calculation. Would this work? |
Given what you're saying about proxies, it could be that we're seeing HTTP sometimes due to testing with Ngrok in our dev environments. I'd need to dig in to see what's happening in production. I think the trick here is that the expected protocol could be either, depending on the scenario. |
Not sure if the library should be doing the HTTP/S proto logic manipulation, but I'm open to discussing. Regarding changing the example for threading reasons, can you submit a PR here? https://github.com/TwilioDevEd/api-snippets/blob/master/guides/request-validation-csharp/example-1/example-1.cs |
@childish-sambino - I'm going to get my colleague @elyahnora to have a look at the HTTP/HTTPS behavior, and get us more information. Regarding the re-write of the RequestValidator attribute, I will submit a PR as soon as I get a chance. |
Since there has been no activity on this issue since March 1, 2020, we are closing this issue. Please feel free to reopen or create a new issue if you still require assistance. Thank you! |
Bit of a shame this issue was closed without at least an update to the documentation which literally tells you to do the wrong thing and in many cases that won't fail until you hit production and experience enough concurrent load. Here's a relevant conversation about these algos and their thread safety. It might be worth targeting net6 and using the new one-time hash methods in Here's a modified version of the repro but with using Twilio.Security;
var validator = new RequestValidator("secret");
Thread thread1 = new Thread(static obj => {
while (true)
{
((RequestValidator)obj).Validate("https://foo.com", "123", "foo");
}
});
Thread thread2 = new Thread(static obj => {
while (true)
{
((RequestValidator)obj).Validate("https://foo.com", "123", "foo");
}
});
thread1.Start(validator);
thread2.Start(validator);
thread1.Join();
thread2.Join(); For now I new up a Aside |
Actually even new'ing up a Edit: Turns out this may just be for correctness, as these algos may not hold onto any unmanaged resources. |
I don't disagree with you Ben that calling new on request validator isn't a great fix, but I don't think it should be the calling method's responsibility to guarantee thread safety. We should probably fix the underlying library, I'm just a little coder team at a non profit, I have not had time to fix this for Twilio 😆 😢 |
@seannybgoode , no worries mate. I wasn't giving you feedback 😉 Your investigation and suggestions were great and a huge help, Thanks for creating the issue! I'll be happy with I'm also happy to create a PR for thread safety ootb if they let me know that the code fix is the way to go, else it really needs to be documented as not thread safe! We hit this on production in our call centre 🙄 @thinkingserious, can you re-open this and let me know:
Out of interest, what do you think is the right way to go here @vcsjones? Are the one-time hash functions appropriate for a hot path like this? That'd remove the thread safety issue entirely for net6+ |
The one shot HMACs are meant to be an "always right" choice. They do not internally allocate, they are thread safe, and they will usually be the fastest. If twilio-csharp/src/Twilio/Security/RequestValidator.cs Lines 14 to 15 in 6b9ca50
Both That said:
Since this project appears to support some quite old .NET Framework releases, I think the "best" choice would be to just create and dispose of the algorithm instances: main...vcsjones:example-thread-safe I wouldn't say that's "bad" considering we still need to support .NET Framework 3.5. Let's write a benchmark. The results:
So yes, the one shot is faster, and the 48 bytes that got allocated are only because we asked the HMAC to return a But, we are still in "nanosecond" territory. Scaling that up to seconds, the So, if you want to ".NET 6-ify" to get access to the one shots, there is a benefit but I don't know that adding support for it is worth the trouble. If performance is paramount, then giving .NET 6 users the one shots seems worthwhile. It'd look something like: byte[] mac;
#if NET6_0_OR_GREATER
mac = HMACSHA1.HashData(_hmacKey, _hmacData);
#else
using (HMACSHA1 hmac = new HMACSHA1(_hmacKey))
{
mac = hmac.ComputeHash(_hmacData);
}
#endif
// Now do something with "mac" here You would still need to add a I hope this has been helpful. |
For what it's worth, the So we create at least one Missing a dispose won't leak because safe handles are used, so the garbage collector and the finalizers should kick in. However, disposing of them when done with them is the correct thing to do. The one shots use different APIs that do not require them to keep track of the lifetime of a native handle, except on Windows 7, I believe. |
@benmccallum - Thank you for the kind words, I'm glad we were able to help diagnose your call center issue. (yikes!) Glad to see that this issue is getting some traction finally. Thanks all. |
Thanks @vcsjones , that was very enlightening and well explained! I was thinking along the same lines given the old target frameworks, but I'm glad you backed that up with benchmarks! Given the docs don't mention this class isn't thread-safe, my take on that is it should be OOTB, so the Certainly for our system when we've only got <30 call centre staff, I think we can safely say the throughput is enough 😄, and I doubt many people are running a system where 1,426,900 ops/sec of Twilio callback verifications is not enough to support their operations; certainly they'd be scaling out at that point anyway! I'll spin up a PR. |
I'm pretty disappointed that this hasn't been fixed after having been pointed out for almost 3 years now.. best case an exception is randomly thrown, worst case a segfault randomly kills the process!!! This is a huge issue IMO We were using basically this implementation from the documentation (can still see it at https://web.archive.org/web/20220630075743/https://www.twilio.com/docs/usage/tutorials/how-to-secure-your-csharp-aspnet-core-app-by-validating-incoming-twilio-requests):
Which was recently updated to:
and it seems to fix the race condition at least. |
Not sure why this was closed - this issue still remains. Try executing the following code which sort of simulates a load. Sometimes it works without issue and generates a valid response. Sometimes it doesn't. After reverse engineering the
|
We have a transient Cryptography Exception being raised by RequestValidator.cs with the error:
"Hash not valid for use in specified state."
Described in detail by me here:
https://stackoverflow.com/questions/54699303/transient-system-security-cryptography-cryptographicexception-in-twiliorequestva
Doing some reading, and digging around in RequestValidator.cs, it looks like the private member _hmac, may not be thread safe. The attribute implementation as directed by Twilio documentation (linked in the stackoverflow post), is only initialized once. As such, the private member may be called by multiple threads concurrently, and possibly cause the failure under load.
My source for that is here: "https://stackoverflow.com/questions/26592596/why-does-sha1-computehash-fail-under-high-load-with-many-threads"
However the supporting documentation to that comment doesn't mention anything about thread safety, although HMACSHA1 does inherit from the same base-class.
Version:5.26
Code Snippet
See the stackoverflow post.
Exception/Log
Steps to Reproduce
The text was updated successfully, but these errors were encountered: