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

fix: parse fragmented sslv2 client hellos #4425

Merged
merged 7 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions tests/unit/s2n_client_hello_recv_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,45 @@ int main(int argc, char **argv)
EXPECT_SUCCESS(s2n_disable_tls13_in_test());
};

/* Test: Parse fragmented sslv2 client hello.
*
* Even if the sslv2 client hello is sent in one packet, there is no requirement
* that our first call to conn->recv returns the whole message. sslv2 uses separate
* record parsing code, so we need to ensure that those paths can handle partial reads.
*/
{
DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
EXPECT_SUCCESS(s2n_connection_set_config(server, tls12_config));

struct s2n_stuffer server_in = { 0 };
uint8_t sslv2_client_hello[] = {
SSLv2_CLIENT_HELLO_HEADER,
SSLv2_CLIENT_HELLO_PREFIX,
SSLv2_CLIENT_HELLO_CIPHER_SUITES,
SSLv2_CLIENT_HELLO_CHALLENGE,
};
EXPECT_SUCCESS(s2n_blob_init(&server_in.blob,
sslv2_client_hello, sizeof(sslv2_client_hello)));
EXPECT_SUCCESS(s2n_connection_set_recv_io_stuffer(&server_in, server));

/* Read message one byte at a time */
s2n_blocked_status blocked = S2N_NOT_BLOCKED;
for (size_t i = 0; i < sizeof(sslv2_client_hello); i++) {
EXPECT_ERROR_WITH_ERRNO(
s2n_negotiate_until_message(server, &blocked, SERVER_HELLO),
S2N_ERR_IO_BLOCKED);
EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_READ);
EXPECT_SUCCESS(s2n_stuffer_skip_write(&server_in, 1));
}

/* Successfully read the full message */
EXPECT_OK(s2n_negotiate_until_message(server, &blocked, SERVER_HELLO));
EXPECT_EQUAL(server->client_protocol_version, S2N_TLS12);
EXPECT_EQUAL(server->client_hello_version, S2N_SSLv2);
EXPECT_TRUE(server->client_hello.sslv2);
};

s2n_config_free(tls12_config);
s2n_config_free(tls13_config);
s2n_cert_chain_and_key_free(chain_and_key);
Expand Down
163 changes: 163 additions & 0 deletions tests/unit/s2n_record_read_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

#include "s2n_test.h"
#include "testlib/s2n_testlib.h"
#include "tls/s2n_record.h"
#include "tls/s2n_tls.h"
#include "utils/s2n_safety.h"

#define SSLV2_MIN_SIZE 3

int main(int argc, char *argv[])
{
BEGIN_TEST();

/* Test s2n_sslv2_record_header_parse */
{
const struct {
uint8_t bytes[S2N_TLS_RECORD_HEADER_LENGTH];
uint16_t length;
uint8_t type;
uint8_t version;
} test_cases[] = {
{
.bytes = { S2N_TLS_SSLV2_HEADER_FLAG, SSLV2_MIN_SIZE, TLS_CLIENT_HELLO, 0x03, 0x03 },
.length = 0,
.type = TLS_CLIENT_HELLO,
.version = S2N_TLS12,
},
{
.bytes = { S2N_TLS_SSLV2_HEADER_FLAG, SSLV2_MIN_SIZE + 1, TLS_CLIENT_HELLO, 0x03, 0x03 },
.length = 1,
.type = TLS_CLIENT_HELLO,
.version = S2N_TLS12,
},
{
.bytes = { S2N_TLS_SSLV2_HEADER_FLAG, 0xFF, TLS_CLIENT_HELLO, 0x03, 0x03 },
.length = 0xFF - SSLV2_MIN_SIZE,
.type = TLS_CLIENT_HELLO,
.version = S2N_TLS12,
},
{
.bytes = { S2N_TLS_SSLV2_HEADER_FLAG, 0x84, TLS_CLIENT_HELLO, 0x03, 0x03 },
.length = 0x84 - SSLV2_MIN_SIZE,
.type = TLS_CLIENT_HELLO,
.version = S2N_TLS12,
},
{
.bytes = { 0x84, 0x84, TLS_CLIENT_HELLO, 0x03, 0x03 },
.length = 0x484 - SSLV2_MIN_SIZE,
.type = TLS_CLIENT_HELLO,
.version = S2N_TLS12,
},
{
.bytes = { 0xFF, 0xFF, TLS_CLIENT_HELLO, 0x03, 0x03 },
.length = 0x7FFF - SSLV2_MIN_SIZE,
.type = TLS_CLIENT_HELLO,
.version = S2N_TLS12,
},
{
.bytes = { S2N_TLS_SSLV2_HEADER_FLAG, SSLV2_MIN_SIZE, 0, 0x03, 0x03 },
.length = 0,
.type = 0,
.version = S2N_TLS12,
},
{
.bytes = { S2N_TLS_SSLV2_HEADER_FLAG, SSLV2_MIN_SIZE, 77, 0x03, 0x03 },
.length = 0,
.type = 77,
.version = S2N_TLS12,
},
{
.bytes = { S2N_TLS_SSLV2_HEADER_FLAG, SSLV2_MIN_SIZE, TLS_SERVER_HELLO, 0x03, 0x03 },
.length = 0,
.type = TLS_SERVER_HELLO,
.version = S2N_TLS12,
},
{
.bytes = { S2N_TLS_SSLV2_HEADER_FLAG, SSLV2_MIN_SIZE, TLS_CLIENT_HELLO, 0x03, 0x04 },
.length = 0,
.type = TLS_CLIENT_HELLO,
.version = S2N_TLS13,
},
{
.bytes = { S2N_TLS_SSLV2_HEADER_FLAG, SSLV2_MIN_SIZE, TLS_CLIENT_HELLO, 0, 0 },
.length = 0,
.type = TLS_CLIENT_HELLO,
.version = 0,
},
};

/* Test: parse valid record headers */
for (size_t i = 0; i < s2n_array_len(test_cases); i++) {
DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(conn);

EXPECT_SUCCESS(s2n_stuffer_write_bytes(&conn->header_in,
test_cases[i].bytes, sizeof(test_cases[i].bytes)));

uint8_t type = 0, version = 0;
uint16_t length = 0;
EXPECT_SUCCESS(s2n_sslv2_record_header_parse(conn, &type, &version, &length));
EXPECT_EQUAL(test_cases[i].type, type);
EXPECT_EQUAL(test_cases[i].version, version);
EXPECT_EQUAL(test_cases[i].length, length);

EXPECT_BYTEARRAY_EQUAL(conn->header_in_data,
test_cases[i].bytes, sizeof(test_cases[i].bytes));

EXPECT_EQUAL(s2n_stuffer_data_available(&conn->header_in), S2N_TLS_RECORD_HEADER_LENGTH);
}

/* Test: parse header with bad length
*
* We already read 3 bytes by reading the header, so logically the message
* is at least 3 bytes long.
*/
{
DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(conn);
EXPECT_SUCCESS(s2n_stuffer_skip_write(&conn->header_in, S2N_TLS_RECORD_HEADER_LENGTH));
conn->header_in_data[0] = S2N_TLS_SSLV2_HEADER_FLAG;

uint8_t type = 0, version = 0;
uint16_t length = 0;

conn->header_in_data[1] = SSLV2_MIN_SIZE - 1;
EXPECT_FAILURE_WITH_ERRNO(
s2n_sslv2_record_header_parse(conn, &type, &version, &length),
S2N_ERR_BAD_MESSAGE);
EXPECT_EQUAL(s2n_stuffer_data_available(&conn->header_in), 3);
EXPECT_SUCCESS(s2n_stuffer_reread(&conn->header_in));

conn->header_in_data[1] = SSLV2_MIN_SIZE;
EXPECT_SUCCESS(s2n_sslv2_record_header_parse(conn, &type, &version, &length));
EXPECT_EQUAL(s2n_stuffer_data_available(&conn->header_in), S2N_TLS_RECORD_HEADER_LENGTH);
EXPECT_SUCCESS(s2n_stuffer_reread(&conn->header_in));

conn->header_in_data[1] = 0;
EXPECT_FAILURE_WITH_ERRNO(
s2n_sslv2_record_header_parse(conn, &type, &version, &length),
S2N_ERR_BAD_MESSAGE);
EXPECT_EQUAL(s2n_stuffer_data_available(&conn->header_in), 3);
EXPECT_SUCCESS(s2n_stuffer_reread(&conn->header_in));
};
};

END_TEST();
}
18 changes: 0 additions & 18 deletions tests/unit/s2n_record_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -411,24 +411,6 @@ int main(int argc, char **argv)
EXPECT_FAILURE_WITH_ERRNO(s2n_record_parse(conn), S2N_ERR_DECRYPT);
};

/* Test s2n_sslv2_record_header_parse fails when fragment_length < 3 */
{
EXPECT_SUCCESS(s2n_stuffer_wipe(&conn->header_in));

uint8_t record_type = 0;
uint8_t protocol_version = 0;
uint16_t fragment_length = 0;

/* First two bytes are the fragment length */
uint8_t header_bytes[] = { 0x00, 0x00, 0x00, 0x00, 0x00 };
EXPECT_SUCCESS(s2n_stuffer_write_bytes(&conn->header_in, header_bytes, sizeof(header_bytes)));

EXPECT_FAILURE_WITH_ERRNO(s2n_sslv2_record_header_parse(conn, &record_type, &protocol_version, &fragment_length), S2N_ERR_SAFETY);

/* Check the rest of the stuffer has not been read yet */
EXPECT_EQUAL(s2n_stuffer_data_available(&conn->header_in), 3);
};

/* Record version is recorded for the first message received (Client Hello) */
{
DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER),
Expand Down
2 changes: 2 additions & 0 deletions tls/s2n_record.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

#define S2N_TLS_CONTENT_TYPE_LENGTH 1

#define S2N_TLS_SSLV2_HEADER_FLAG (0x80)

/* All versions of TLS define the record header the same:
* ContentType + ProtocolVersion + length
*/
Expand Down
25 changes: 16 additions & 9 deletions tls/s2n_record_read.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,21 @@ int s2n_sslv2_record_header_parse(
uint8_t *client_protocol_version,
uint16_t *fragment_length)
{
struct s2n_stuffer *in = &conn->header_in;
struct s2n_stuffer *header_in = &conn->header_in;
lrstewart marked this conversation as resolved.
Show resolved Hide resolved

S2N_ERROR_IF(s2n_stuffer_data_available(in) < S2N_TLS_RECORD_HEADER_LENGTH, S2N_ERR_BAD_MESSAGE);
POSIX_ENSURE(s2n_stuffer_data_available(header_in) >= S2N_TLS_RECORD_HEADER_LENGTH,
S2N_ERR_BAD_MESSAGE);

POSIX_GUARD(s2n_stuffer_read_uint16(in, fragment_length));
POSIX_GUARD(s2n_stuffer_read_uint16(header_in, fragment_length));
/* The first bit of the length must be set to indicate SSLv2,
* so the raw fragment length must be adjusted.
*/
*fragment_length ^= (S2N_TLS_SSLV2_HEADER_FLAG << 8);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wrote a bunch of unit tests for this in s2n_record_read_test, but still please sanity check my math :)

Copy link
Contributor

Choose a reason for hiding this comment

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

The shift math is correct 😄. I'm just a bit concerned about the XOR: #4425 (comment)

lrstewart marked this conversation as resolved.
Show resolved Hide resolved

/* The SSLv2 header is only a 2 byte record length (technically 3 bytes if
* padding is included, but s2n-tls assumes no padding).
* See https://www.ietf.org/archive/id/draft-hickman-netscape-ssl-00.txt.
* padding is included, but TLS1.2 requires no padding).
* See https://datatracker.ietf.org/doc/html/rfc5246#appendix-E.2
lrstewart marked this conversation as resolved.
Show resolved Hide resolved
* And https://www.ietf.org/archive/id/draft-hickman-netscape-ssl-00.txt.
*
* So by reading 5 bytes for a standard header we have also read the first
* 3 bytes of the record payload. s2n-tls only supports SSLv2 ClientHellos,
Expand All @@ -54,16 +60,16 @@ int s2n_sslv2_record_header_parse(
* read a standard header, we need to adjust the length so that we only
* try to read the remainder of the record payload.
*/
POSIX_ENSURE_GTE(*fragment_length, 3);
*fragment_length -= 3;
POSIX_ENSURE(*fragment_length >= s2n_stuffer_data_available(header_in), S2N_ERR_BAD_MESSAGE);
*fragment_length -= s2n_stuffer_data_available(header_in);
lrstewart marked this conversation as resolved.
Show resolved Hide resolved

/*
* The first field of an SSLv2 ClientHello is the msg_type.
*
* This is always '1', matching the ClientHello msg_type used by later
* handshake messages.
*/
POSIX_GUARD(s2n_stuffer_read_uint8(in, record_type));
POSIX_GUARD(s2n_stuffer_read_uint8(header_in, record_type));

/*
* The second field of an SSLv2 ClientHello is the version.
Expand All @@ -73,10 +79,11 @@ int s2n_sslv2_record_header_parse(
* See s2n_sslv2_client_hello_recv.
*/
uint8_t protocol_version[S2N_TLS_PROTOCOL_VERSION_LEN] = { 0 };
POSIX_GUARD(s2n_stuffer_read_bytes(in, protocol_version, S2N_TLS_PROTOCOL_VERSION_LEN));
POSIX_GUARD(s2n_stuffer_read_bytes(header_in, protocol_version, S2N_TLS_PROTOCOL_VERSION_LEN));

*client_protocol_version = (protocol_version[0] * 10) + protocol_version[1];

POSIX_GUARD(s2n_stuffer_reread(header_in));
return 0;
}

Expand Down
5 changes: 2 additions & 3 deletions tls/s2n_recv.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,14 @@ int s2n_read_full_record(struct s2n_connection *conn, uint8_t *record_type, int
POSIX_GUARD(s2n_stuffer_resize_if_empty(&conn->in, S2N_LARGE_FRAGMENT_LENGTH));

/* Read the record until we at least have a header */
POSIX_GUARD(s2n_stuffer_reread(&conn->header_in));
POSIX_GUARD_RESULT(s2n_read_in_bytes(conn, &conn->header_in, S2N_TLS_RECORD_HEADER_LENGTH));

uint16_t fragment_length;

/* If the first bit is set then this is an SSLv2 record */
if (conn->header_in.blob.data[0] & 0x80) {
conn->header_in.blob.data[0] &= 0x7f;
if (conn->header_in.blob.data[0] & S2N_TLS_SSLV2_HEADER_FLAG) {
*isSSLv2 = 1;

WITH_ERROR_BLINDING(conn, POSIX_GUARD(s2n_sslv2_record_header_parse(conn, record_type, &conn->client_protocol_version, &fragment_length)));
} else {
WITH_ERROR_BLINDING(conn, POSIX_GUARD(s2n_record_header_parse(conn, record_type, &fragment_length)));
Expand Down
Loading