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

tls: store SslSocket::transport_socket_options_ in SSL object #21679

Merged
merged 14 commits into from
Jun 27, 2022

Conversation

danzh2010
Copy link
Contributor

@danzh2010 danzh2010 commented Jun 13, 2022

Commit Message: Store a pointer to shared pointer SslSocket::transport_socket_options_ in SSL object via SSL_set_app_data() for both Client and Server ContextImpl. The old behavior is storing the pointer to the object pointed by shared pointer SslSocket::transport_socket_options_, and only for ClientContextImpl.

This change make it possible the caller of SSL_get_app_data() can extend the life time of the object pointed by SslSocket::transport_socket_options_.

Additional Description: interface change to ContextImpl::newSsl()
Risk Level: high, behavioral change in client cert validation, but no external behavioral change.
Testing: existing ssl socket tests pass
Docs Changes: N/A
Release Notes: N/A
Platform Specific Features: N/A
Runtime guard: N/A

Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
@danzh2010
Copy link
Contributor Author

/assign @RyanTheOptimist

Copy link
Contributor

@RyanTheOptimist RyanTheOptimist left a comment

Choose a reason for hiding this comment

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

This looks like a nice small, PR. WOO HOO! :) How hard would it be to verify the new (server-side) behavior in unit tests?

auto transport_socket_options_shared_ptr_ptr =
static_cast<const Network::TransportSocketOptionsConstSharedPtr*>(SSL_get_app_data(ssl));
ASSERT(transport_socket_options_shared_ptr_ptr);
transport_socket_options = (*transport_socket_options_shared_ptr_ptr).get();
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the only difference between these two branches the ASSERT()? I can't quite seem to convince myself of it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the new code path dereferences the pointer twice.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm. So line 441 calls SSL_get_app_data(ssl) and casts it to TransportSocketOptionsConstSharedPtr*. but link 446 calls the same method and casts it to TransportSocketOptions*. This is confusing to me. What is the actual object stored in SSL_get_app_data? Maybe the type of the object is different in the old/new codepath?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, in the old path SSL_set_app_data() takes the TransportSocketOptions* which is from TransportSocketOptionsConstSharedPtr, but the in the new path SSL_set_app_data() takes a pointer to TransportSocketOptionsConstSharedPtr itself.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I see. Thanks for the explanation!

Storing a pointer to a shared pointer sets off my spidey sense a bit. A shared pointer implies the that object is owned by whoever has the last shared pointer to it, whereas a pointer implies that the object is owned elsewhere. But I guess SSL_set_app_data() requires a raw pointer and we can't put a smart pointer in there, right? And I guess this is safe because we require the caller to own Options and make it outlive the context, right?

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, I see from the PR description that you want the shared_ptr so that you can extend the lifetime of the object via what's stored in the SSL. Can you elaborate on the use case for this?

Given that this PR doesn't yet have any external behavior change, it might simplify to remove the runtime guard.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But I guess SSL_set_app_data() requires a raw pointer and we can't put a smart pointer in there, right?

Yes

And I guess this is safe because we require the caller to own Options and make it outlive the context, right?

SSL_get_app_data() is called within the life time of a ssl socket, so it's safe. And after retrieving the pointer to the shared_ptr, the async cert validator is responsible to extends the life time of the socket option.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, I see from the PR description that you want the shared_ptr so that you can extend the lifetime of the object via what's stored in the SSL. Can you elaborate on the use case for this?

The ability to extend the life time of socket options will be very helpful in async cert validation. Given that ssl socket and context can be gone in the middle.

Given that this PR doesn't yet have any external behavior change, it might simplify to remove the runtime guard.

I wasn't quite sure about the runtime guard policy so I used one just in case, given that it's risky behavioral change. That being said, if I don't need to runtime guard the change, ssl socket will always storing the same type into SSL object. So your concern about storing different types in different conditions won't exist. Which one is better?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's better in this case to not runtime guard it; it makes the code simpler, which reduces the risk of a logic error.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, reverted runtime guard. PTAL!

Copy link
Member

@lizan lizan left a comment

Choose a reason for hiding this comment

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

xref: #9867

We might want to create a struct for SSL_set/get_app_data.

@@ -606,7 +622,9 @@ bssl::UniquePtr<SSL> ClientContextImpl::newSsl(const Network::TransportSocketOpt
}

if (options && !options->verifySubjectAltNameListOverride().empty()) {
SSL_set_app_data(ssl_con.get(), options);
if (!tls_async_cert_validation_) {
SSL_set_app_data(ssl_con.get(), options.get());
Copy link
Member

Choose a reason for hiding this comment

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

Let's always set pointer to the shared ptr? That simplifies the SSL_get_app_data logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This if condition is the runtime guard, so is the call site of SSL_get_app_data(). With the guard being true, newSsl() will always set pointer to the shared ptr.

@danzh2010
Copy link
Contributor Author

xref: #9867

We might want to create a struct for SSL_set/get_app_data.

I'm not sure about the direction of xref: #9867. I agree we should use only one interface for the same purpose. But SSL_set_ex_data() allows the SSL object to points to multiple places with different indexes, while SSL_set_app_data() only allows one. We could combine extended socket info and transport socket option into a struct, but we also could use SSL_set_ex_data() with different indexes for them. To me SSL_set_ex_data() seems to provide more flexibility and we don't need to refactor ssl socket interfaces.

Anyway, can I keep this PR as it before we clearly know which direction to go?

Signed-off-by: Dan Zhang <danzh@google.com>
@danzh2010
Copy link
Contributor Author

How hard would it be to verify the new (server-side) behavior in unit tests?

Added a unit test for that.

Copy link
Contributor

@RyanTheOptimist RyanTheOptimist left a comment

Choose a reason for hiding this comment

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

Thanks for adding a test!

auto transport_socket_options_shared_ptr_ptr =
static_cast<const Network::TransportSocketOptionsConstSharedPtr*>(SSL_get_app_data(ssl));
ASSERT(transport_socket_options_shared_ptr_ptr);
transport_socket_options = (*transport_socket_options_shared_ptr_ptr).get();
Copy link
Contributor

Choose a reason for hiding this comment

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

Hm. So line 441 calls SSL_get_app_data(ssl) and casts it to TransportSocketOptionsConstSharedPtr*. but link 446 calls the same method and casts it to TransportSocketOptions*. This is confusing to me. What is the actual object stored in SSL_get_app_data? Maybe the type of the object is different in the old/new codepath?

Signed-off-by: Dan Zhang <danzh@google.com>
@RyanTheOptimist
Copy link
Contributor

/wait

@danzh2010
Copy link
Contributor Author

/wait

What's the action item?

@RyanTheOptimist
Copy link
Contributor

/wait

What's the action item?

I asked a question last week about the casting on line 441/446 and I don't see a reply to it. #21679 (comment)

Oh, bizarre! Man, github is just the worst UI in history. I don't see a reply on the main page, but if click to the file I see a response there. I'm so sorry about that. I'll take a look now.

Copy link
Contributor

@RyanTheOptimist RyanTheOptimist left a comment

Choose a reason for hiding this comment

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

I'm really sorry for the delay! I think this LGTM, but I'd like your confirmation on a question before merging.

auto transport_socket_options_shared_ptr_ptr =
static_cast<const Network::TransportSocketOptionsConstSharedPtr*>(SSL_get_app_data(ssl));
ASSERT(transport_socket_options_shared_ptr_ptr);
transport_socket_options = (*transport_socket_options_shared_ptr_ptr).get();
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I see. Thanks for the explanation!

Storing a pointer to a shared pointer sets off my spidey sense a bit. A shared pointer implies the that object is owned by whoever has the last shared pointer to it, whereas a pointer implies that the object is owned elsewhere. But I guess SSL_set_app_data() requires a raw pointer and we can't put a smart pointer in there, right? And I guess this is safe because we require the caller to own Options and make it outlive the context, right?

Copy link
Contributor

@ggreenway ggreenway left a comment

Choose a reason for hiding this comment

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

/wait

auto transport_socket_options_shared_ptr_ptr =
static_cast<const Network::TransportSocketOptionsConstSharedPtr*>(SSL_get_app_data(ssl));
ASSERT(transport_socket_options_shared_ptr_ptr);
transport_socket_options = (*transport_socket_options_shared_ptr_ptr).get();
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be better to just store the raw pointer, not a pointer to smartptr, if we have to guarantee the lifetime already.

// We use the first certificate for a new SSL object, later in the
// SSL_CTX_set_select_certificate_cb() callback following ClientHello, we replace with the
// selected certificate via SSL_set_SSL_CTX().
return bssl::UniquePtr<SSL>(SSL_new(tls_contexts_[0].ssl_ctx_.get()));
auto ssl_con = bssl::UniquePtr<SSL>(SSL_new(tls_contexts_[0].ssl_ctx_.get()));
if (tls_async_cert_validation_) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we remove this branch and set the app data unconditionally? It makes it easier to reason about what data is set and when, especially since we lose all type-safety when stashing things in the SSL.

transport_socket_options = (*transport_socket_options_shared_ptr_ptr).get();
} else {
transport_socket_options =
static_cast<const Network::TransportSocketOptions*>(SSL_get_app_data(ssl));
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not a fan of storing different types here depending on options. It makes it harder to reason about (given lack of type safety), and much harder to sort it out in gdb if it crashes. I think it would be better to just have 2 indexes, and store the appropriate data in each index. Then if the wrong one is read, it will be a nullptr instead of a valid pointer of the wrong type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are you suggesting to switch to SSL_get_ex_data() with a non-zero index completely?

Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
@danzh2010
Copy link
Contributor Author

sync'ed with main and removed runtime guard. PTAL

@danzh2010
Copy link
Contributor Author

Ping for a review after merge!

@ggreenway ggreenway merged commit f14eee8 into envoyproxy:main Jun 27, 2022
Amila-Rukshan pushed a commit to Amila-Rukshan/envoy that referenced this pull request Jun 28, 2022
…roxy#21679)

Store a pointer to shared pointer SslSocket::transport_socket_options_ in SSL object via SSL_set_app_data() for both Client and Server ContextImpl. The old behavior is storing the pointer to the object pointed by shared pointer SslSocket::transport_socket_options_, and only for ClientContextImpl.

This change make it possible the caller of SSL_get_app_data() can extend the life time of the object pointed by SslSocket::transport_socket_options_.

Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Amila Senadheera <amila.15@cse.mrt.ac.lk>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants