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

OpenSSLInitializer isn't threadsafe #1739

Closed
neotron opened this issue May 31, 2017 · 16 comments
Closed

OpenSSLInitializer isn't threadsafe #1739

neotron opened this issue May 31, 2017 · 16 comments
Assignees
Milestone

Comments

@neotron
Copy link
Contributor

neotron commented May 31, 2017

The initialize and uninitialize methods in OpenSSLInitializer uses a construct like this:

if (++_rc == 1) { ... }

_rc is a Poco::AtomicCounter.

This is insufficient since you can easily get into a situation where both uninitialize and initialize is called at the same time from multiple threads. These methods should be protected by a mutex lock to ensure proper execution.

The workaround to keep a persistent instance of this object works of course, but that just avoids triggering the race condition.

@neotron
Copy link
Contributor Author

neotron commented May 31, 2017

Additional information: This issue was reported in issue #1498, which was closed due to the workaround solving the issue.

neotron added a commit to neotron/poco that referenced this issue May 31, 2017
The init/uninit methods can be called from multiple threads, and thus need synchronization with a mutex.
@aleks-f aleks-f self-assigned this Jul 3, 2017
@aleks-f aleks-f added the bug label Jul 3, 2017
@aleks-f aleks-f added this to the Release 1.8.0 milestone Jul 3, 2017
@micheleselea
Copy link
Contributor

I do not understand: Atomic counter should be "atomic". I don't understand the problem here: I use it in some multi threaded environments and I thought AtomicCounter was atomic. why is wrong?

@aleks-f
Copy link
Member

aleks-f commented Jul 10, 2017

Atomic itself is indeed atomic; performing ++ on it is atomic too, but performing == to compare it with something else is not. It follows that, in a multi-thread situation, you can end up doing multiple ++'s before any comparison happens.

@micheleselea
Copy link
Contributor

mmm I don't get it.
if we do something like
int i=++_rc;
should be thread safe or not? I think yes....or at least I thought, otherwise I do not know what does it means AmoticCounter, and I think we can get the same problem on destroying SharedPtr isnt'it?
otherwise if the code above is thread safe we should check
if(i==1)
without problem...so even the original code should be thread safe....what am I missing?

@micheleselea
Copy link
Contributor

micheleselea commented Jul 10, 2017

The problem is with increments and decrements in a multithreaded environment? because if we just increment I do not see problem
So probably we should use a standard int and a Mutex to have thread safe. And I think we can get same problem with SharedPtr that use ReferenceCounter that use AtomicCounter

@micheleselea
Copy link
Contributor

And if so we have the same problem in all Poco::Notification because RefCountedObject is used

@aleks-f
Copy link
Member

aleks-f commented Jul 10, 2017

if there was only one place where _rc is modified, there would be no problem, but there is also uninitialize(), where -- is done, so you may end up with some crazy scenarios (although I don't think this is very frequent). I'm not sure about these other cases you mention, will take a look eventually

@micheleselea
Copy link
Contributor

micheleselea commented Jul 10, 2017

Ok now it's quite clear, probably we have the problem even in other cases I talk about, because we have AtomicCounter incremented decremented and checked (==) in multithread environment. Probably we usually do all the increments and after all the decrements and probably that way should not be a problem. But it's anyway some kind of condition we have to check carefully because we can get into trouble otherwise

@obiltschnig
Copy link
Member

It's a bit more complicated than that. This is a specific problem of OpenSSLInitializer, because of the

if (++_rc == 1) ...

leading to a race condition. It is only an issue if you have multiple threads calling OpenSSLInitializer::initialize() simultaneously. Most programs should not have this issue because you should initialise OpenSSL and all other libraries you use in the main thread.

The specific problem is that in a multithreaded scenario a thread (that had ++_rc == 1 evaluate to false because _rc is >= 1) may start using OpenSSL before the other thread (that had ++_rc == 1 evaluate to true) has finished initialising it.

The actual comparison is not an issue, because you are comparing with the result of the atomic operation, not with the actual value at the time of comparison (which could be different already).

With Poco::SharedPtr, Poco::AutoPtr and Poco::RefCountedObject this is not an issue because after

if (--_counter == 0) delete this;

in a well-formed program, if _counter == 1, there can only be one single instance of the smart pointer left. You could have the situation where one thread runs the destructor of the last smart pointer while another thread at the same time copies it (increasing the reference count). This can lead to a problematic situation, but then, the other thread is accessing a dead object, so you have a bug anyway.

@aleks-f
Copy link
Member

aleks-f commented Jul 17, 2017

All correct, except that the thread about to use the object is accessing a dead object. That thread has obtained the pointer and may access a live object, while the other one just happens to be done with it and is about to delete it - that is precisely what the shared pointer should prevent from happening. It is likely a rare scenario, but I think it's a legitimate concern.

Generally speaking, comparison of an incremented or decremented atomic variable is thread-safe, as long as it is (a) done in one place only or (b) same operation is done in multiple places and compared with same value. When the opposite operations (probably the most frequent scenario), or comparisons with different values are done in multiple places, it is not thread-safe.

@obiltschnig
Copy link
Member

What you want would be more like std::experimental::atomic_shared_ptr that std::shared_ptr. In any case, having two threads access the same SharedPtr instance (as opposed to different SharedPtr instances pointing to the same object) without external synchronisation is a really bad idea.

@obiltschnig
Copy link
Member

And, the comparison after the atomic increment/decrement is not the issue, as you're comparing the result of the atomic operation, not the current (by the time the comparison happens) value.

@obiltschnig
Copy link
Member

And regarding usage of the same SharedPtr (or AutoPtr) instance from multiple threads. Good luck if one thread assigns a new pointer (causing deletion of the object previously pointed to) while the other thread currently calls a method of that object through the same SharedPtr.

@micheleselea
Copy link
Contributor

micheleselea commented Jul 18, 2017

I think the problem is still there even if you have two SharedPtr instances to the same object in two different thread. you can occur in the rare case above. The problem is if you have your reference counter incremented end decremented by different thread. Is not the use case you usually use SharedPtr because u usually create all the instances you need, so incrementing, and than decrementing on destroying. But probably the safest way to do the check is using a Mutex like a OpenSSLInitializer (or using some atomic_shard_ptr).
I agree with you that you should never have the same SharedPtr instance accessed by two different thread

@aleks-f
Copy link
Member

aleks-f commented Jul 18, 2017

Of course, the rule should be to copy SharedPtr when used by multiple threads and we should not concern ourselves with what users do. However, there is only one instance of the reference counter, shared by all SharedPtr copies. So, when one thread has obtained 0 reference count here, the other may subsequently increment it to 1 here. But I agree that such scenario is a bug because it means copying the last SharedPtr that is being destroyed, which will typically happen in the owning object destructor.

As for atomics, yes I was wrong - after atomic increment/decrement, there is a local copy of the value and, regardless of what other threads do, the equality criteria will be satisfied in one thread only.

@obiltschnig
Copy link
Member

Multithreading is hard ;-) Back then when I did the change to use AtomicCounter I spend quite some time thinking things through. Still, things like OpenSSLInitializer can happen :-(

aleks-f pushed a commit that referenced this issue Jul 18, 2017
* Fix OpenSSLInitialized thread safety (#1739)

The init/uninit methods can be called from multiple threads, and thus need synchronization with a mutex.

* Renamed mutex variable and use ScopedLock.

* Change reference count variable to be an integer, since it’s protected by a mutex and no longer needs to be atomic.
@aleks-f aleks-f added the fixed label Jul 18, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants