# wolfssl before 5.5.1: CVE-2022-39173 Buffer overflow when refining
cipher suites
==================================================================================
## INFO
=======
The CVE project has assigned the id CVE-2022-39173 to this issue.
Severity: high 7.5
Affected version: before 5.5.1
End of embargo: The embargo for this vulnerability ended 29th of September, 2022
## SUMMARY
==========
In wolfSSL before 5.5.1 malicious clients can cause a buffer-overflow
during a resumed TLS 1.3 handshake. If an attacker resumes a previous
TLS session by sending a maliciously crafted Client Hello, followed by
another maliciously crafted Client Hello. In total 2 Client Hellos
have to be sent. One which pretends to resume a previous session and a
second one as a response to a Hello Retry Request message.
The malicious Client Hellos contain a list of supported cipher suites,
which contain at least `⌊sqrt(150)⌋ + 1 = 13` duplicates and less than
150 ciphers in total. The buffer-overflow occurs in the `RefineSuites`
function. An overflow of 44700 bytes has been confirmed. Therefore,
large portions of the stack can get overwritten, including return
addresses.
We confirmed the vulnerability by sending packets over TCP to a
Wolfssl server, freshly built from the sources with the
`--enable-session-ticket` flags (or simply `--enable-all`). We can
provide sources for our software (tlspuffin) that produce those
packets (and that automatically found the attack trace). The command
given at the end of this document triggers the buffer overflow.
It is very likely that there is a way to craft an exploit which can
cause a RCE. We have not yet created such an exploit as it would
likely depend on the memory layout of the binary which uses wolfSSL.
Moreover, the size of the overflow can be fine-tuned in order to not
smash the stack and continue the execution with a too large length of
suites buffer and that will cause other routines that iterate over
thus buffer (e.g., `FindSuiteSSL`) to misbehave. Hypothetically, this
might be exploited to make the server use a cipher it should not
accept such as `nullcipher` that would open up new attack vectors such
as downgrade attacks.
While this has not been confirmed yet, the buffer overflow itself has
been confirmed.
## DETAILS
==========
Line numbers below are valid for the wolfSSL Git tag
[v5.4.0-stable](https://github.com/wolfSSL/wolfssl/tree/v5.4.0-stable).
The bug we found is in the `RefineSuites` function. In the following
we want to explain why the function is able to overflow the `suites`
array.
```c
/* Refine list of supported cipher suites to those common to server and client.
*
* ssl SSL/TLS object.
* peerSuites The peer's advertised list of supported cipher suites.
*/
static void RefineSuites(WOLFSSL* ssl, Suites* peerSuites)
{
byte suites[WOLFSSL_MAX_SUITE_SZ];
word16 suiteSz = 0;
word16 i, j;
XMEMSET(suites, 0, WOLFSSL_MAX_SUITE_SZ);
for (i = 0; i < ssl->suites->suiteSz; i += 2) {
for (j = 0; j < peerSuites->suiteSz; j += 2) {
if (ssl->suites->suites[i+0] == peerSuites->suites[j+0] &&
ssl->suites->suites[i+1] == peerSuites->suites[j+1]) {
suites[suiteSz++] = peerSuites->suites[j+0];
suites[suiteSz++] = peerSuites->suites[j+1];
}
}
}
ssl->suites->suiteSz = suiteSz;
XMEMCPY(ssl->suites->suites, &suites, sizeof(suites));
#ifdef WOLFSSL_DEBUG_TLS
[...]
#endif
}
```
tls13.c:4355
The `RefineSuites` function expects a `WOLFSSL` struct which contains
a list of acceptable ciphers suites (`ssl->suites->suites`), as well
as an array of peer cipher suites (`peerSuites`). Both inputs are
bounded by `WOLFSSL_MAX_SUITE_SZ`, which is equal to 300 bytes or 150
cipher suites.
Let us assume that `ssl->suites` consists of a single cipher suite
like `TLS_AES_256_GCM_SHA384` and the `peerSuites` list contains the
same cipher repeated thirteen times. The `RefineSuites` function will
iterate for each element in `ssl->suites` over `peerSuites` and append
the suite to `suites` if it is a match. The `suites` array has a
maximum length of `WOLFSSL_MAX_SUITE_SZ == 300 bytes == 150 suites`.
With the just mentioned example input, the length of `suites` will now
equal thirteen. The `suites` array is now copied to the `WOLFSSL`
struct in the last line of the listing above. Therefore, `ssl->suites`
contains now thirteen times the `TLS_AES_256_GCM_SHA384` cipher suite.
Let us now call the same `RefineSuites` function again on the modified
`WOLFSSL` struct and the same `peerSuites` list. The `RefineSuites`
function will iterate for each element in `ssl->suites` over
`peerSuites` and append the suite to `suites` if it is a match.
Because `ssl->suites` contains already 13 times the
`TLS_AES_256_GCM_SHA384` cipher suite, in total 13 x 13 = 169 cipher
suites are written to `suites`. 169 cipher suites require 338 bytes,
which is more than what's available on the stack. The `suites` buffer
overflows.
The maximum size of `peerSuites` is 150 cipher suites. Therefore, an
overflow of 44700 bytes is possible and has been confirmed.
The buffer `ssl->suites->suites` is supposed to be reset to only
contain the acceptable ciphers at each session start, and thus
initially contains no duplicate. However, by provoking a `HELLO CLIENT
RETRY REQUEST`, it is possible to make the server call `RefineSuites`
twice as explained next.
## TRIGGERING THE BUFFER OVERFLOW
=================================
In order to cause the above buffer-overflow, it is required to call
`RefineSuites` twice. Malicious clients need to perform the handshake
in a certain way to reach this situation.
The buffer overflow at the attacked server can be obtained at least in
the following situation:
1. Resume the previous session by sending a second Client Hello
(`CH2`) with the following criteria:
- Exclude the `support_group_extension`, to cause a Hello Retry Request
- Include a binder which cryptographically binds this session to
the previous one.
- Include a list of cipher suites that contains a repetition of `n`
times the same cipher `c` with `13 <= n < 150`, deemed acceptable by
the server.
The server will parse this message, enters the state
`SERVER_HELLO_RETRY_REQUEST_COMPLETE` and stores at least `n` times
the cipher `c` in `ssl->suites->suites` by calling `RefineSuites`.
2. Sending a third Client Hello (`CH3`) with the same criteria as in step 2.
The server will parse this message and because
`ssl->suites->suites` already contains `n` times the cipher `c`,
`RefineSuites` will write in `suites` at least until `suites[nˆ2]`
which overflows since `nˆ2 > 300`.
## DETAILS ABOUT STEP 1.
========================
During step 2., we want to cause the server to perform a Hello Retry Request.
This is possible by not sending a supported group in the `CH2`. By not
sending a support group extension, the function
`TLSX_SupportedGroups_Find` will return false.
```c
static int TLSX_SupportedGroups_Find(WOLFSSL* ssl, word16 name)
{
...
/* Check consistency now - extensions in any order. */
if (!TLSX_SupportedGroups_Find(ssl, clientKSE->group))
continue;
...
```
tls.c:8374
This will cause clientKSE to be `NULL` and `doHelloRetry` will be set to 1.
```c
int TLSX_KeyShare_Establish(WOLFSSL *ssl, int* doHelloRetry)
{
...
/* No supported group found - send HelloRetryRequest. */
if (clientKSE == NULL) {
/* Set KEY_SHARE_ERROR to indicate HelloRetryRequest required. */
*doHelloRetry = 1;
return TLSX_KeyShare_SetSupported(ssl);
}
...
```
tls.c:9273
Finally, the server enters the state
`SERVER_HELLO_RETRY_REQUEST_COMPLETE` in the function
`VerifyServerSuite` while verifying the server suite when processing
`CH2`.
```c
/* Make sure server cert/key are valid for this suite, true on success
* Returns 1 for valid server suite or 0 if not found
* For asynchronous this can return WC_PENDING_E
*/
static int VerifyServerSuite(WOLFSSL* ssl, word16 idx)
{
...
if (IsAtLeastTLSv1_3(ssl->version) &&
ssl->options.side == WOLFSSL_SERVER_END) {
int doHelloRetry = 0;
/* Try to establish a key share. */
int ret = TLSX_KeyShare_Establish(ssl, &doHelloRetry);
if (doHelloRetry) {
ssl->options.serverState = SERVER_HELLO_RETRY_REQUEST_COMPLETE;
}
...
}
...
```
tls.c:30688
## DETAILS ABOUT STEP 2.
====================
The server is now in a state in which it expects another Client Hello
(`CH3`) from the client.
The server is now in the state `SERVER_HELLO_RETRY_REQUEST_COMPLETE`
and will process the third ClientHello (`CH3`) with the call of
`ProcessReply` before reaching the `TLS13_ACCEPT_SECOND_REPLY_DONE`
state.
```c
int wolfSSL_accept_TLSv13(WOLFSSL* ssl)
{
...
case TLS13_ACCEPT_FIRST_REPLY_DONE :
if (ssl->options.serverState ==
SERVER_HELLO_RETRY_REQUEST_COMPLETE) {
ssl->options.clientState = CLIENT_HELLO_RETRY;
while (ssl->options.clientState < CLIENT_HELLO_COMPLETE) {
if ((ssl->error = ProcessReply(ssl)) < 0) {
WOLFSSL_ERROR(ssl->error);
return WOLFSSL_FATAL_ERROR;
}
}
}
ssl->options.acceptState = TLS13_ACCEPT_SECOND_REPLY_DONE;
WOLFSSL_MSG("accept state ACCEPT_SECOND_REPLY_DONE");
FALL_THROUGH;
...
```
tls13.c:10909
## VULNERABILITY VARIANTS
=========================
### Adjusting the overflow
Note that the length of the list of ciphers in `CH2` does not
necessarily have to be the same as the one of `CH1` and can be
adjusted to fine-tune the size of the overflow.
### Make the server parse `CH2`
Note also that, on the contrary to `CH1`, `CH2` does not necessarily
have to put the server in the `SERVER_HELLO_RETRY_REQUEST_COMPLETE`
state (which should be forbidden by the TLS 1.3 RFC) or make it return
an error, and can thus contain a supported group, which could be
included to possibly make the server continue the processing of `CH2`
without returning an error.
We have confirmed that we can make the server parses `CH2` until the
end and starts computing a Server Hello with `ssl->suites->suiteSz`
that exceeds 300.
### Resuming an existent session
It is also possible to trigger the vulnerability by trying to resume
an existent and genuine session established through a full initial
handshake (step 0.):
0. Sending an initial genuine Client Hello (`CH1`) to the server and
then completing a full handshake, thus establishing a PSK.
## EXPLOITATION
===============
We suspect that it is possible to craft an exploit which could lead to
RCE if any of the above bytes coincides with the memory address of
executable code. Depending on the memory layout of the binary it could
be possible to gain RCE.
More bytes could be used to overflow `suites` if more ciphers were
configured to be accepted with the server, e.g., with options like
`--enable-blake2`.
We confirmed that this could also be exploited to smash the stack and
cause the server to crash with a segmentation fault by using a large
list of ciphers.
Finally, by fine-tuning the length of the overflow and by including
the supported group in `CH3`, it could be possible to make the server
process `CH3` with a `ssl->suites->suites->suiteSz` value that exceeds
300. This way, routines like `FindSuiteSSL` that will iterate over
`ssl->suites->suites` (allocated on 300 bytes) until
`ssl->suites->suiteSz` (>300) will also iterate over bytes that
contain other fields such as `ssl->suites->hashSigAlgo`. It is likely
that this could be exploited to make such routines return arbitrary
values. For example, it might be exploited to make the server use a
cipher it should not accept such as `nullcipher`; thus breaking
confidentiality.
## FURTHER CONCERNS
==================
We observed that the server is accepting the `CH2` Client Hello
message and issues a Hello Retry Request, even though `CH2` does not
contain supported groups. Clients are not allowed to add the supported
groups extension in the retry Client Hello (`CH3`) according to the
RFC 8446 in section
[4.1.2](https://www.rfc-editor.org/rfc/rfc8446#section-4.1.2). The
addition of supported groups is not allowed when retrying the Client
Hello.
We suggest aborting the handshake when receiving `CH2` instead of
offering the client a retry.