Skip to content

Refactor the ex_data initialization to allow multiple inits#8828

Closed
levitte wants to merge 1 commit intoopenssl:masterfrom
levitte:fix-ex_data-20190425
Closed

Refactor the ex_data initialization to allow multiple inits#8828
levitte wants to merge 1 commit intoopenssl:masterfrom
levitte:fix-ex_data-20190425

Conversation

@levitte
Copy link
Member

@levitte levitte commented Apr 25, 2019

With multiple library contexts, it's possible that the same module
runs its init and teardown functions multiple times during its
lifetime. This would normally not cause much trouble, except that our
FIPS module is a bit special and will include code that handles a
library context internally, just to be able to make internal EVP
calls.

This means that the provider init function will potentially be called
multiple times, and the teardown as well, and those may do
initializations and deinitializations, including the ex_data cleanup.
This may mean that the ex_data lock gets freed too early (on first
cleanup instead of last).

To ensure that the ex_data lock isn't freed before its last use, we
associate a reference counter with it, which gets incremented for
everytime there's an attempt to create that lock, and gets decremented
when there's an attempt to clean it up, thereby ensuring that
construction will only happen on first init and destruction on last
deinit.

Related to #8728

With multiple library contexts, it's possible that the same module
runs its init and teardown functions multiple times during its
lifetime.  This would normally not cause much trouble, except that our
FIPS module is a bit special and will include code that handles a
library context internally, just to be able to make internal EVP
calls.

This means that the provider init function will potentially be called
multiple times, and the teardown as well, and those may do
initializations and deinitializations, including the ex_data cleanup.
This may mean that the ex_data lock gets freed too early (on first
cleanup instead of last).

To ensure that the ex_data lock isn't freed before its last use, we
associate a reference counter with it, which gets incremented for
everytime there's an attempt to create that lock, and gets decremented
when there's an attempt to clean it up, thereby ensuring that
construction will only happen on first init and destruction on last
deinit.

Related to openssl#8728
@levitte levitte added the branch: master Applies to master branch label Apr 25, 2019
@levitte levitte added this to the 3.0.0 milestone Apr 25, 2019
paulidale
paulidale previously approved these changes Apr 25, 2019
@paulidale paulidale added the approval: done This pull request has the required number of approvals label Apr 25, 2019
@mattcaswell
Copy link
Member

Travis failures appear relevant.

int i;

return RUN_ONCE(&ex_data_init_flag, do_ex_data_init)
&& CRYPTO_UP_REF(&ex_data_lock_count, &i, ex_data_lock_count_lock);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look right. We'll end up incrementing the ref count every time someone calls get_and_lock.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, you're right. Damnit!

This is quite infuriating, it seems that we're fighting a losing battle to avoid having to deal with shared object init/fini, DllMain or whatever different platforms use, while trying to perform exactly what they are designed for.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey....did I ever mention openssl_ctx_runone()... ;-)

@richsalz
Copy link
Contributor

Aren't you going to have to do this for every module that has an init?

@mattcaswell
Copy link
Member

Aren't you going to have to do this for every module that has an init?

Not really. This FIPS module is a bit of a special case. Most modules have no business having their own copy of the ex_data code. Either they won't use it at all, or they will link with libcrypto and use the libcrypto code for it. This is only necessary because we're bringing this stuff inside the module boundary.

@richsalz
Copy link
Contributor

Or you expose a few callbacks for get-index/fetch/store and let the module call them into the core?

@richsalz
Copy link
Contributor

I am concerned how two copies of the exdata stuff will work on platforms where DLL's are in a separate memory address space. For example, Windows.

@levitte
Copy link
Member Author

levitte commented Apr 26, 2019

I am concerned how two copies of the exdata stuff will work on platforms where DLL's are in a separate memory address space. For example, Windows.

libcrypto will have its own ex_data and the FIPS module will have its ex_data. That's what we want, isn't it?

@richsalz
Copy link
Contributor

who allocates the exdata array in an object -- which malloc, and how do you know to free it?
who allocates the unique indices to identify objects, and how do you avoid conflicts?

we want modules to have their own INSTANCES of ex_data, but not the infrastructure; that should (must) be shared.

@levitte
Copy link
Member Author

levitte commented Apr 27, 2019

we want modules to have their own INSTANCES of ex_data, but not the infrastructure; that should (must) be shared.

Wellllll, depends. We're dealing with a special case, the FIPS module, which has its own copy of everything it needs, and that turned out to include the ex_data stuff (which it would have either way, sooner or later). The FIPS module will not share anything with libcrypto, i.e. the application.

@mattcaswell
Copy link
Member

we want modules to have their own INSTANCES of ex_data, but not the infrastructure; that should (must) be shared.

No - that absolutely must not be shared in the FIPS module. Or rather, the source can be shared, but libcrypto and the FIPS module have to have their own independent copies. You cannot leave the FIPS module boundary to do crypto work.

@richsalz
Copy link
Contributor

You can leave the FIPS module boundary to do things like allocate memory and assign pointers; malloc, etc. The crypto code must be within the module. I suggest you ask the lab to confirm your conclusion, and to understand why error-stack callbacks are okay, but not pointer-allocation.

@richsalz
Copy link
Contributor

What will happen on windows if I create a FIPS-managed object and my application adds some exdata, and then I free the FIPS object. Which part, core or module, free's which resources?

@levitte
Copy link
Member Author

levitte commented Apr 29, 2019

How will you application add exdata to the provider side object? As far as I know, there's no interface to do so, so I wonder how you will accomplish that.

@levitte
Copy link
Member Author

levitte commented Apr 29, 2019

I've been giving this PR quite a bit of thought, and found that this isn't the right spot for this approach. Closing it without merge.

@levitte levitte closed this Apr 29, 2019
@richsalz
Copy link
Contributor

To answer your question: the application will still have an RSA object, or an X509 object, or an SSL object, and it can make ex_data calls there, no?

@levitte
Copy link
Member Author

levitte commented Apr 29, 2019

Yes. But that's not provider side data, at least so far.

@richsalz
Copy link
Contributor

The list of pointers in the ex_data is currently planned to be in the provider and the unique ex_data numeric index could now be not unique (since it exists in the provider and the core). I think both of those things are a mistake, and I have tried to point it out before.

@levitte
Copy link
Member Author

levitte commented Apr 29, 2019

You're mixing things up. OPENSSL_CTX is implemented as an ex_data, but it's not tied to any other object (so definitely not part of a RSA object, X509 object, ...)

@richsalz
Copy link
Contributor

No, I am not mixing things up. I am talking only about ex_data and unique indices. I am sorry if I am not making myself clear.

@levitte
Copy link
Member Author

levitte commented Apr 30, 2019

Well...

Regarding ex_data attached to an RSA structure (or even an EVP_PKEY, if there is an ex_data in that structure), there's no reason whatsoever why it should be passed to the provider.

Regarding indices, regardless of where they are created, they will be created in the entity (shared library, module) that has the functionality. If that functionality is in a module (such as the FIPS module), then the identities will be module local.

@richsalz
Copy link
Contributor

richsalz commented May 1, 2019

Only a module can add exdata? Can an application add exdata? How do you keep those from getting entangled, such as by re-using an index (one created in-module one created in the core)?

@mattcaswell
Copy link
Member

Only a module can add exdata? Can an application add exdata? How do you keep those from getting entangled, such as by re-using an index (one created in-module one created in the core)?

Objects do not cross the libcrypto<->provider boundary. You cannot (for example) create an RSA object in libcrypto and pass it to the provider. Similarly objects created in the provider don't get passed back to libcrypto. All we do is pass "void *" pointers around. If we need to pass an RSA object it would get converted into an OSSL_PARAM and that would get passed.

There is no possibility of ex_data on the provider side getting confused with libcrypto side ex_data. They are entirely separate.

@richsalz
Copy link
Contributor

richsalz commented May 1, 2019

Similarly objects created in the provider don't get passed back to libcrypto

This confuses me. Please show sample code for how an application creates a FIPS-module RSA keypair.

@levitte
Copy link
Member Author

levitte commented May 1, 2019

This confuses me. Please show sample code for how an application creates a FIPS-module RSA keypair.

Roughly speaking, the application would call EVP_PKEY_keygen(), which finds the corresponding provider function, calls it, and gets a void * back that points at the provider object that holds the key. The contents of that object is opaque to libcrypto, but the pointer to that object would be stored in the EVP_PKEY that EVP_PKEY_keygen() constructs.

In other words, in a provider-powered OpenSSL, an EVP_PKEY holds some libcrypto-specific data and a pointer to the key data that's owned by the provider.

@richsalz
Copy link
Contributor

richsalz commented May 1, 2019

But EVP_PKEY_keygen outputs an EVP_PKEY object, not a void * Are you saying it's not really a PKEY? And that the functions that only a subset of the functions that work on EVP_PKEY will now work? If so, which ones, and that in particular, the ex_data functions (which are described as working on various key types) are now going to fail?

@levitte
Copy link
Member Author

levitte commented May 1, 2019

But EVP_PKEY_keygen outputs an EVP_PKEY object, not a void * Are you saying it's not really a PKEY?

Huh? Here's what I wrote:

but the pointer to that object would be stored in the EVP_PKEY that EVP_PKEY_keygen() constructs.

@mattcaswell
Copy link
Member

As far as the application is concerned it would have an EVP_PKEY and can do everything with an EVP_PKEY that it normally would. If you look inside that EVP_PKEY though you won't find an RSA object anyway. All you will find is a void * pointer. Any function calls that need to do RSA type things get delegated down to the provider and the void * pointer is passed. On the provider side that void * gets cast into a provider side RSA object and it services the requests as required. The provider side RSA object never leaves the provider except as a void * pointer.

@mattcaswell
Copy link
Member

On the provider side that void * gets cast into a provider side RSA object

Actually its probably more accurate to say that it gets cast into some provider side equivalent of the EVP_PKEY object that contains the RSA object.

@levitte
Copy link
Member Author

levitte commented May 2, 2019

On the provider side that void * gets cast into a provider side RSA object

Actually its probably more accurate to say that it gets cast into some provider side equivalent of the EVP_PKEY object that contains the RSA object.

Uhmmm, I think this is saying too much, conceptually speaking. You have no idea how the provider module for BLARGH will look, all you know is that this void * will point to whatever that provider module chooses to structure its keys.

@levitte
Copy link
Member Author

levitte commented May 2, 2019

BTW, I've reasons to believe this should be re-opened, and then reworked slightly.

@levitte levitte reopened this May 2, 2019
@levitte levitte dismissed paulidale’s stale review May 2, 2019 10:26

Dismiss approval because rework is needed

@levitte
Copy link
Member Author

levitte commented May 2, 2019

No. I'm deciding finally that no. There's a fundamental init problem that this can't solve. I'm closing this (again) in favor of #8857

@levitte levitte closed this May 2, 2019
@richsalz
Copy link
Contributor

richsalz commented May 2, 2019

How does an application set exdata on what EVP_PKEY_keygen returns? What API?

@levitte
Copy link
Member Author

levitte commented May 2, 2019

How does an application set exdata on what EVP_PKEY_keygen returns? What API?

There is currently no ex_data in an EVP_PKEY. That will have to be added.

... and this leads me to another thing: this discussion is off topic here, especially since this PR is closed. I suggest you raise an issue about ex_data in EVP_PKEYs and that we discuss it there.

@richsalz
Copy link
Contributor

richsalz commented May 2, 2019

Excellent point! See #8863

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approval: done This pull request has the required number of approvals branch: master Applies to master branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants