Skip to content

Add 1RTT secrets into TlsSecrets for openssl#5222

Merged
guhetier merged 5 commits into
microsoft:mainfrom
nhorman:5218
Jul 31, 2025
Merged

Add 1RTT secrets into TlsSecrets for openssl#5222
guhetier merged 5 commits into
microsoft:mainfrom
nhorman:5218

Conversation

@nhorman

@nhorman nhorman commented Jul 3, 2025

Copy link
Copy Markdown
Contributor

When doing the initial authoring, I neglected to add the 1RTT secrets into the TlsSecrets struct, meaning the SSLKEYLOG file wasn't populated with those values. Fix that up

Fixes #5218

When doing the initial authoring, I neglected to add the 1RTT secrets
into the TlsSecrets struct, meaning the SSLKEYLOG file wasn't populated
with those values.  Fix that up

Fixes microsoft#5218
@nhorman
nhorman requested a review from a team as a code owner July 3, 2025 15:55
@ManickaP

ManickaP commented Jul 4, 2025

Copy link
Copy Markdown
Member

I just tested this branch and the buffers are still empty. Seems like you're not setting TlsContext->TlsSecrets->SecretLength, e.g.:

TlsContext->TlsSecrets->SecretLength = (uint8_t)SecretLen;

@nhorman

nhorman commented Jul 4, 2025

Copy link
Copy Markdown
Contributor Author

Addressed

nibanks
nibanks previously approved these changes Jul 6, 2025

@nibanks nibanks left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM 👍

@codecov

codecov Bot commented Jul 6, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.71%. Comparing base (2623c07) to head (fa88d15).
⚠️ Report is 30 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5222      +/-   ##
==========================================
- Coverage   86.74%   84.71%   -2.03%     
==========================================
  Files          59       59              
  Lines       18330    18331       +1     
==========================================
- Hits        15901    15530     -371     
- Misses       2429     2801     +372     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

ManickaP
ManickaP previously approved these changes Jul 7, 2025

@ManickaP ManickaP left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks, re-tested, works for me.

Comment thread src/platform/tls_openssl.c Outdated
@nhorman
nhorman dismissed stale reviews from ManickaP and nibanks via 2a6d17a July 7, 2025 17:23
Comment thread src/platform/tls_openssl.c
guhetier
guhetier previously approved these changes Jul 15, 2025

@guhetier guhetier left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Minor nitpick because of the strange comment style of this codebase, looks good to me otherwise

Co-authored-by: Guillaume Hetier <hetier.guillaume@gmail.com>
guhetier
guhetier previously approved these changes Jul 15, 2025
@guhetier

Copy link
Copy Markdown
Collaborator

There are multiple test failures in the linux test pass that seems relevant. The Windows kernel failure can be ignored (known issue).

@nhorman

nhorman commented Jul 16, 2025

Copy link
Copy Markdown
Contributor Author

I agree that
https://github.com/microsoft/msquic/actions/runs/16298098148/job/46026351183?pr=5222
Isn't relevant as its using the schannel backend

I also think that
https://github.com/microsoft/msquic/actions/runs/16298098148/job/46026351380?pr=5222

Isn't relevant, as its using quictls, rather than openssl.

https://github.com/microsoft/msquic/actions/runs/16298098148/job/46026351373?pr=5222
Is definately relevant, specifically a heap use after free, unsure of the root cause, looking into it

@nhorman

nhorman commented Jul 16, 2025

Copy link
Copy Markdown
Contributor Author

Eww, ok, I see the failure (I think, we're getting use after free conditions on this stack):

==66145==ERROR: AddressSanitizer: heap-use-after-free on address 0x61b000001144 at pc 0x7ffb5da320f5 bp 0x7ffb4d5f1cc0 sp 0x7ffb4d5f1cb0
WRITE of size 1 at 0x61b000001144 thread T12
    #0 0x7ffb5da320f4 in QuicTlsYieldSecret /__w/msquic/msquic/src/platform/tls_openssl.c:639
    #1 0x7ffb5dae4949 in quic_new_record_layer ../../../../../../../submodules/openssl/ssl/quic/quic_tls.c:184
    #2 0x7ffb5daf2ffb in ssl_set_new_record_layer ../../../../../../../submodules/openssl/ssl/record/rec_layer_s3.c:1420
    #3 0x7ffb5dadfab8 in tls13_change_cipher_state ../../../../../../../submodules/openssl/ssl/tls13_enc.c:748
    #4 0x7ffb5db742c5 in tls_process_finished ../../../../../../../submodules/openssl/ssl/statem/statem_lib.c:923
    #5 0x7ffb5db3d465 in read_state_machine ../../../../../../../submodules/openssl/ssl/statem/statem.c:689
    #6 0x7ffb5db3d465 in state_machine ../../../../../../../submodules/openssl/ssl/statem/statem.c:483
    #7 0x7ffb5da9429d in SSL_do_handshake ../../../../../../../submodules/openssl/ssl/ssl_lib.c:4975
    #8 0x7ffb5da39925 in CxPlatTlsProcessData /__w/msquic/msquic/src/platform/tls_openssl.c:3178
    #9 0x7ffb5d939b04 in QuicCryptoProcessData /__w/msquic/msquic/src/core/crypto.c:1959
    #10 0x7ffb5d93b9f8 in QuicCryptoProcessFrame /__w/msquic/msquic/src/core/crypto.c:1347
    #11 0x7ffb5d90cc34 in QuicConnRecvFrames /__w/msquic/msquic/src/core/connection.c:4612
    #12 0x7ffb5d9145c3 in QuicConnRecvDatagramBatch /__w/msquic/msquic/src/core/connection.c:5594
    #13 0x7ffb5d917905 in QuicConnRecvDatagrams /__w/msquic/msquic/src/core/connection.c:5777
    #14 0x7ffb5d9190e5 in QuicConnFlushRecv /__w/msquic/msquic/src/core/connection.c:5947
    #15 0x7ffb5d926e8c in QuicConnDrainOperations /__w/msquic/msquic/src/core/connection.c:7823
    #16 0x7ffb5d8b6a5c in QuicWorkerProcessConnection /__w/msquic/msquic/src/core/worker.c:579
    #17 0x7ffb5d8ba7dd in QuicWorkerLoop /__w/msquic/msquic/src/core/worker.c:740
    #18 0x7ffb5d8bb882 in QuicWorkerThread /__w/msquic/msquic/src/core/worker.c:808
    #19 0x7ffb5bc94ac2 in start_thread nptl/pthread_create.c:442
    #20 0x7ffb5bd2684f  (/lib/x86_64-linux-gnu/libc.so.6+0x12684f)

That is a dereference of the TlsSecrets pointer in QuicTlsYieldSecret, after its been freed here:

0x61b000001144 is located 708 bytes inside of 1416-byte region [0x61b000000e80,0x61b000001408)
freed by thread T0 here:
    #0 0x7ffb5f4b6e57 in operator delete[](void*) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:163
    #1 0x563122bca153 in UniquePtr<QUIC_TLS_SECRETS []>::~UniquePtr() /__w/msquic/msquic/src/inc/msquic.hpp:401
    #2 0x563122bca153 in QuicTestConnectAndPing /__w/msquic/msquic/src/test/lib/DataTest.cpp:638
    #3 0x56312239abd3 in WithSend0RttArgs1_Send0Rtt_Test::TestBody() /__w/msquic/msquic/src/test/bin/quic_gtest.cpp:1945
    #4 0x563122e0f29f in void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) /__w/msquic/msquic/submodules/googletest/googletest/src/gtest.cc:2729
    #5 0x563122dfc2c0 in testing::Test::Run() /__w/msquic/msquic/submodules/googletest/googletest/src/gtest.cc:2746
    #6 0x563122dfc61c in testing::TestInfo::Run() /__w/msquic/msquic/submodules/googletest/googletest/src/gtest.cc:2892
    #7 0x563122dfd9ea in testing::TestSuite::Run() /__w/msquic/msquic/submodules/googletest/googletest/src/gtest.cc:3070
    #8 0x563122e02a53 in testing::internal::UnitTestImpl::RunAllTests() /__w/msquic/msquic/submodules/googletest/googletest/src/gtest.cc:6062
    #9 0x563122e0f760 in bool testing::internal::HandleExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /__w/msquic/msquic/submodules/googletest/googletest/src/gtest.cc:2729
    #10 0x563122dfc357 in testing::UnitTest::Run() /__w/msquic/msquic/submodules/googletest/googletest/src/gtest.cc:5602
    #11 0x563122360344 in RUN_ALL_TESTS() /__w/msquic/msquic/submodules/googletest/googletest/include/gtest/gtest.h:2334
    #12 0x563122360344 in main /__w/msquic/msquic/src/test/bin/quic_gtest.cpp:2786
    #13 0x7ffb5bc29d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58

It appears the QuicTestConnectAndPing() function allocates a TlsSecrets array, starts a connection, waits for the ClientStats and ServerStats completion events, checks to make sure that the underlying TLS backend set EarlyTrafficSecrets, and then exits.

I'm guessing that, because openssl yields read and write secrets independently, The completion events are triggered before the server side read secret is yielded, and because we only check after that for early traffic secrets, the test exits, allowing the UniquePtrs that hold the ClientSecrets and ServerSecrets to be deallocated, when there is still a pending Callback to QuicTlsYieldSecret comming. The TLS backend gets that callback, and dereferences the TlsSecrets pointer which is now deallocated, and...boom.

I'd say the easy fix would be to convert the UniquePtrs to SharedPtrs, but I'm pretty sure the shared_ptr semantics dont work in C code.

The alternate solution I think is to shutdown the connection before returning, which I think will guarantee that no further calls will be made to QuicTlsYieldSecret()

@nhorman

nhorman commented Jul 16, 2025

Copy link
Copy Markdown
Contributor Author

fixed

@guhetier

Copy link
Copy Markdown
Collaborator

The unique_ptr are implementation rolled out by hand (no STL because our test code runs in kernel mode too).
Hand rolling a shared_ptr will take a bit more efforts.

I'll have to look more into details in the test logic to understand how it is meant to be synchronized so we fix it.

Ideally, we should avoid spining on it and have an event that let us know when we are done.

@nhorman

nhorman commented Jul 16, 2025

Copy link
Copy Markdown
Contributor Author

An event to indicate handshake completion would be ideal, I agree, just not sure how to implement that, so I went with a spin solution for now to get through the issue. Happy to implement something better though if you can think of a way to do it.

@guhetier

Copy link
Copy Markdown
Collaborator

Sorry for taking a while to get back to this.
Looking at the details of the test and your explanation of the issue, have some questions.

Even if it isn't documented clearly, it is generally considered that the secrets will have been set by the time the connection is started (QUIC_CONNECTION_EVENT_CONNECTED). The completion event in the tests should fire when all the streams off all the connections are completed, which would necessarily be after the QUIC_CONNECTION_EVENT_CONNECTED event.

Is the read key set by OpenSSL after the QUIC_CONNECTION_EVENT_CONNECTED?

@nhorman

nhorman commented Jul 23, 2025

Copy link
Copy Markdown
Contributor Author

@guhetier I'm not sure. I can say with confidence that the read key on a server is installed only when the handshake done message is sent, which will be after the write key is installed, as to weather or not that occurs after the QUIC_CONNECTION_EVENT_CONNECTED is sent, I'm unsure.

@guhetier

Copy link
Copy Markdown
Collaborator

@nhorman From our doc:

This [QUIC_CONNECTION_EVENT_CONNECTED] event is delivered when the handshake has completed. This means the peer has been securely authenticated. This happens after one full round trip on the client side. The server side considers the handshake complete once the client responds after this.

Does it sound like the secrets could be set later than this from what you know?

I am not familiar enough to know if when this PR set the secret, we could be past that point (or I'll need to really dive in that code).

If it is expected that they could be provided later, we will need to find an event (existing or not) we can use as the point when the keys have been set.

@nhorman

nhorman commented Jul 24, 2025

Copy link
Copy Markdown
Contributor Author

@guhetier I believe the answer to the question is yes. The write secret will be installed as soon as the encrypted extensions are delivered by the client to the server, but on the server, the read secret may not be installed until the server sends the handshake done message, which sounds to me like it could be after the QUIC_CONNECTION_EVENT_CONNECTED is sent.

@guhetier

Copy link
Copy Markdown
Collaborator

@nhorman Got it.
We should probably update our documentation to clarify the lifetime requirement of the buffers and when the app can know for sure they have been populated.

I'll create a task to follow up on it.

For the test itself, I think that as long as we ensure the secret buffer outlive the registration, we will be safe.
All it takes is to move the buffers declarations before the MsQuicRegistration declaration, so that the destructors run in the proper order. MsQuicRegistration destructor will wait for the connection shutdown.

(apologies for the delay once again)

@nhorman

nhorman commented Jul 30, 2025

Copy link
Copy Markdown
Contributor Author

@guhetier no apologies needed, thank you for getting back on this.

To be clear, I think what you're suggesting is this type of change:

diff --git a/src/test/lib/DataTest.cpp b/src/test/lib/DataTest.cpp
index 630864996..e5a348ebe 100644
--- a/src/test/lib/DataTest.cpp
+++ b/src/test/lib/DataTest.cpp
@@ -419,6 +419,9 @@ QuicTestConnectAndPing(
     }
 #endif
 
+    UniquePtr<QUIC_TLS_SECRETS[]> ClientSecrets;
+    UniquePtr<QUIC_TLS_SECRETS[]> ServerSecrets;
+
     MsQuicRegistration Registration(NULL, QUIC_EXECUTION_PROFILE_TYPE_MAX_THROUGHPUT, true);
     TEST_TRUE(Registration.IsValid());
 
@@ -428,8 +431,6 @@ QuicTestConnectAndPing(
         //
     }
 
-    UniquePtr<QUIC_TLS_SECRETS[]> ClientSecrets;
-    UniquePtr<QUIC_TLS_SECRETS[]> ServerSecrets;
     if (ClientZeroRtt && !ServerRejectZeroRtt) {
         ClientSecrets.reset(
                 new(std::nothrow) QUIC_TLS_SECRETS[ConnectionCount]);

Correct? Unfortunately, that doesn't seem to help. I backed out my spin change from commit ece1f10 locally and applied the above patch, but I get the same asan failure. I'll keep looking into it

@nhorman

nhorman commented Jul 30, 2025

Copy link
Copy Markdown
Contributor Author

actually scrap that last comment, you're suggestion seems to work quite well, problem was that I forgot (as I often do) that the redhat config on my fedora system uses settings that upstream openssl can't parse, so all tests fail if I don't back them out. I'll push changes shortly

We were failing one of the DataTests due to the Client/Server secrets
getting destroyed before the underlying TLS structure was done with
them.

This was occuring because in QuicTestConnectAndPing, the test function
returns before the openssl TLS layer had completing registering the
server side application read key, which isn't needed for the reporting
of an established connection.  The function returned, cleaned the
secrets buffer, after which the TLS layer tried to access them, and
boom.

Thanks to @guhetier, who noted that the destruction of the
MsQuicRegistration forces shutdown of the connection, and blocks on its
completion, so if we move the declaration of the UniquePtr buffers to
before the allocation of said registration, the fact that C++ calls
destructors in reverse order ensures that the Secrets memory remains
valid until the connection is shut down.
@nhorman

nhorman commented Jul 30, 2025

Copy link
Copy Markdown
Contributor Author

updated with @guhetier suggested fix for the asan error

@guhetier
guhetier merged commit 2861d81 into microsoft:main Jul 31, 2025
434 of 437 checks passed
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.

OpenSSL 3.5: QUIC_TLS_SECRETS is missing some fields

4 participants