Skip to content

./configure.py --terminate-on-asserts#2980

Merged
reneme merged 8 commits intomasterfrom
terminate_on_assert
Aug 8, 2022
Merged

./configure.py --terminate-on-asserts#2980
reneme merged 8 commits intomasterfrom
terminate_on_assert

Conversation

@reneme
Copy link
Copy Markdown
Collaborator

@reneme reneme commented May 23, 2022

As discussed, this adds ./configure.py --terminate-on-asserts causing the library to std::abort when an assertion is not met.

The code base seems to use BOTAN_ASSERT* in some places where BOTAN_ARG_CHECK or BOTAN_STATE_CHECK would be more appropriate. Hence, I enabled the new option for the coverage CI run in the hope to catch most of them.

Copy link
Copy Markdown
Owner

@randombit randombit left a comment

Choose a reason for hiding this comment

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

LGTM. Still a few CI failures which I assume are missing cases but once CI is green good to merge

@reneme
Copy link
Copy Markdown
Collaborator Author

reneme commented May 23, 2022

The assertion in the coverage build job seems like a bug, actually.

There are a few BoGo tests that send a prefix string to the TLS server under test. E.g. BoGo test HttpGET sends GET / HTTP/1.0\n" and expects the TLS server under test to notice that and fail. The TLS 1.2 record implementation detects this situation and throws a TLS_Exception:

if(first5 == "GET /" ||
first5 == "PUT /" ||
first5 == "POST " ||
first5 == "HEAD ")
{
throw TLS_Exception(Alert::PROTOCOL_VERSION,
"Client sent plaintext HTTP request instead of TLS handshake");
}

The TLS channel then catches this exception and proceeds to sending a TLS alert in response. TLS_Channel::send_alert() used to swallow all exceptions that happened while trying to send an alert:

try
{
send_record(ALERT, alert.serialize());
}
catch(...) { /* swallow it */ }

But now, the sequence_numbers() accessor is called downstream, causing an assertion because the server didn't actually finish initializing and m_sequence_numbers is still a nullptr:

void Channel_Impl_12::send_record(uint8_t record_type, const std::vector<uint8_t>& record)
{
send_record_array(sequence_numbers().current_write_epoch(),
record_type, record.data(), record.size());
}

I see two possible fixes:

  • initialize m_sequence_numbers before any data is processed, or
  • detect this situation and handle it

But is it actually legal for a TLS server to send a TLS alert as their first flight?

@reneme reneme force-pushed the terminate_on_assert branch from 7787d67 to 9c70d9d Compare May 23, 2022 13:51
@reneme
Copy link
Copy Markdown
Collaborator Author

reneme commented May 23, 2022

But is it actually legal for a TLS server to send a TLS alert as their first flight?

At least for Botan's (D)TLS 1.2 implementation it doesn't seem to be legal for the TLS server to send messages before it successfully received a Client Hello. Only after that do we know whether we speak TLS or DTLS.

Hence, let's just not send TLS alerts in the very early error case described above: 9c70d9d

Comment on lines +26 to 41

DSA_PublicKey::DSA_PublicKey(const AlgorithmIdentifier& alg_id,
const std::vector<uint8_t>& key_bits) :
DL_Scheme_PublicKey(alg_id, key_bits, DL_Group_Format::ANSI_X9_57)
{
BOTAN_ARG_CHECK(group_q().bytes() > 0, "Q parameter must be set for DSA");
}


DSA_PublicKey::DSA_PublicKey(const DL_Group& grp, const BigInt& y1)
{
m_group = grp;
m_y = y1;

BOTAN_ARG_CHECK(grp.q_bytes() > 0, "Q parameter must be set for DSA");
}
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

That is suppose to fix asserts in PK_Verifier for a DSA public key in the cert corpus/fuzzer. I left the assert untouched and instead introduce sanity checks at construction time of the DSA public key.
@randombit I could use a little help with this, as I'm not sure whether other things should also be validated here.

By the way: The currently still failing assert in the pkcs8 fuzzer is related to a DSA private key:

* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
  * frame #0: 0x00007ff81db8e00e libsystem_kernel.dylib`__pthread_kill + 10
    frame #1: 0x00007ff81dbc41ff libsystem_pthread.dylib`pthread_kill + 263
    frame #2: 0x00007ff81db0fd24 libsystem_c.dylib`abort + 123
    frame #3: 0x00000001009a9778 pkcs8`Botan::assertion_failure(char const*, char const*, char const*, char const*, int) + 696
    frame #4: 0x000000010048ed39 pkcs8`Botan::Montgomery_Exponentation_State::exponentiation(Botan::BigInt const&, unsigned long) const + 1001
    frame #5: 0x000000010048fc46 pkcs8`Botan::monty_execute(Botan::Montgomery_Exponentation_State const&, Botan::BigInt const&, unsigned long) + 22
    frame #6: 0x0000000100651218 pkcs8`Botan::DL_Group_Data::power_g_p(Botan::BigInt const&, unsigned long) const + 72
    frame #7: 0x000000010065127b pkcs8`Botan::DL_Group::power_g_p(Botan::BigInt const&, unsigned long) const + 59
    frame #8: 0x0000000100660e4f pkcs8`Botan::DSA_PrivateKey::DSA_PrivateKey(Botan::AlgorithmIdentifier const&, std::__1::vector<unsigned char, Botan::secure_allocator<unsigned char> > const&) + 767
    frame #9: 0x000000010080913c pkcs8`std::__1::__unique_if<Botan::DSA_PrivateKey>::__unique_single std::__1::make_unique<Botan::DSA_PrivateKey, Botan::AlgorithmIdentifier const&, std::__1::vector<unsigned char, Botan::secure_allocator<unsigned char> > const&>(Botan::AlgorithmIdentifier const&, std::__1::vector<unsigned char, Botan::secure_allocator<unsigned char> > const&) + 76
    frame #10: 0x000000010080851b pkcs8`Botan::load_private_key(Botan::AlgorithmIdentifier const&, std::__1::vector<unsigned char, Botan::secure_allocator<unsigned char> > const&) + 1003
    frame #11: 0x000000010083fe17 pkcs8`Botan::PKCS8::(anonymous namespace)::load_key(Botan::DataSource&, std::__1::function<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > ()> const&, bool) + 471
    frame #12: 0x0000000100840587 pkcs8`Botan::PKCS8::load_key(Botan::DataSource&) + 231
    frame #13: 0x0000000100002c8f pkcs8`fuzz(unsigned char const*, unsigned long) + 255
    frame #14: 0x0000000100002b8a pkcs8`LLVMFuzzerTestOneInput + 26
    frame #15: 0x0000000100003483 pkcs8`(anonymous namespace)::fuzz_files(char**) + 899
    frame #16: 0x0000000100002eb7 pkcs8`main + 231
    frame #17: 0x0000000101df551e dyld`start + 462

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I think this assert would trigger if you load a DSA key with a secret key that is greater than the group order.

This likely affects other key types as well.

I think we need to rethink key validation somewhat. Right now all validation is dumped on check_key, because some of the checks (testing primality and ECC point validity) are expensive. But most are simple range checks and could be cheaply done during construction or deserialization.

This is resolved with 1735f95 right?

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 25, 2022

Codecov Report

Merging #2980 (efcc70b) into master (4c58fd5) will decrease coverage by 0.06%.
The diff coverage is 88.88%.

❗ Current head efcc70b differs from pull request most recent head 5f8bca9. Consider uploading reports for the commit 5f8bca9 to get more accurate results

@@            Coverage Diff             @@
##           master    #2980      +/-   ##
==========================================
- Coverage   92.58%   92.52%   -0.07%     
==========================================
  Files         596      577      -19     
  Lines       69779    66792    -2987     
  Branches     6617     6400     -217     
==========================================
- Hits        64608    61801    -2807     
+ Misses       5138     4958     -180     
  Partials       33       33              
Impacted Files Coverage Δ
src/lib/utils/assert.cpp 42.10% <0.00%> (-57.90%) ⬇️
src/lib/asn1/asn1_time.cpp 91.78% <100.00%> (ø)
src/lib/modes/aead/eax/eax.cpp 97.77% <100.00%> (ø)
src/lib/pubkey/dsa/dsa.cpp 97.70% <100.00%> (+0.29%) ⬆️
src/lib/pubkey/rsa/rsa.cpp 97.00% <100.00%> (ø)
src/lib/tls/tls12/tls_channel_impl_12.cpp 89.59% <100.00%> (+0.54%) ⬆️
src/tests/test_tls_utils.cpp 0.00% <0.00%> (-83.34%) ⬇️
src/lib/utils/thread_utils/semaphore.cpp 69.23% <0.00%> (-30.77%) ⬇️
src/fuzzer/ressol.cpp 85.71% <0.00%> (-7.15%) ⬇️
... and 70 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

const secure_vector<uint8_t>& key_bits) :
DL_Scheme_PrivateKey(alg_id, key_bits, DL_Group_Format::ANSI_X9_57)
{
BOTAN_ARG_CHECK(m_x.bits() <= m_group.q_bits(), "x must not be larger than q");
Copy link
Copy Markdown
Collaborator Author

@reneme reneme May 25, 2022

Choose a reason for hiding this comment

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

I have a hunch that this check should better be constant time, because:

BigInt Montgomery_Exponentation_State::exponentiation(const BigInt& scalar, size_t max_k_bits) const
{
BOTAN_DEBUG_ASSERT(scalar.bits() <= max_k_bits);
// TODO add a const-time implementation of above assert and use it in release builds

... some guidance on how to do that would be appreciated.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I think this is safe enough to not be constant time in this context. bits itself is constant time, and if we reject that means the key itself is just manifest invalid. So there is no real info leak.

We should check x <= q though rather than just the bit size, since x == q would be accepted, but isn't a valid DSA key.

Copy link
Copy Markdown
Owner

@randombit randombit left a comment

Choose a reason for hiding this comment

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

LGTM, modulo changing DSA key check to use value instead of bit length.

@reneme reneme force-pushed the terminate_on_assert branch from 1735f95 to efcc70b Compare June 1, 2022 13:21
DL_Scheme_PrivateKey(alg_id, key_bits, DL_Group_Format::ANSI_X9_57)
{
BOTAN_ARG_CHECK(m_x > 0, "x must be greater than zero");
BOTAN_ARG_CHECK(m_x < m_group.get_q(), "x must not be larger than q");
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I added a check that x > 0 because the failing fuzzer test (pkcs8) caused negative numbers to be passed into this constructor.

On that note: I feel that DL_Group should also validate that it is always instantiated with positive numbers, no?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

On that note: I feel that DL_Group should also validate that it is always instantiated with positive numbers, no?

It should. I think if p or q is zero or negative that will cause Modular_Reducer to throw an exception. I don't think g would be caught. Also we are not checking other invariants like g < p

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Nevertheless, I propose this is something for a follow-up PR. I wanted to keep this to the minimum to just start enforcing assertions in the first place.

@reneme reneme force-pushed the terminate_on_assert branch from efcc70b to 5f8bca9 Compare August 8, 2022 07:03
@reneme
Copy link
Copy Markdown
Collaborator Author

reneme commented Aug 8, 2022

I rebased to master to let the CI run once again after this was stale for more than a month. Once that comes back clean, I'll merge.

@reneme reneme merged commit a78ea35 into master Aug 8, 2022
@reneme reneme deleted the terminate_on_assert branch August 9, 2022 08:57
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.

3 participants