-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Conversation
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>
/assign @RyanTheOptimist |
There was a problem hiding this 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(); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
There was a problem hiding this 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()); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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>
Added a unit test for that. |
There was a problem hiding this 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(); |
There was a problem hiding this comment.
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>
/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. |
There was a problem hiding this 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(); |
There was a problem hiding this comment.
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?
There was a problem hiding this 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(); |
There was a problem hiding this comment.
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_) { |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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>
sync'ed with main and removed runtime guard. PTAL |
Ping for a review after merge! |
…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>
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 pointerSslSocket::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