Skip to content

Commit

Permalink
fix: handle fragmented sslv2 client hellos
Browse files Browse the repository at this point in the history
  • Loading branch information
lrstewart committed Feb 18, 2024
1 parent 20010e6 commit edb3afb
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 25 deletions.
48 changes: 48 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,54 @@ 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));

DEFER_CLEANUP(struct s2n_stuffer server_in = { 0 }, s2n_stuffer_free);
EXPECT_SUCCESS(s2n_stuffer_growable_alloc(&server_in, 0));
EXPECT_SUCCESS(s2n_connection_set_recv_io_stuffer(&server_in, server));

s2n_blocked_status blocked = S2N_NOT_BLOCKED;

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_stuffer_write_bytes(&server_in,
sslv2_client_hello, sizeof(sslv2_client_hello)));

/* Do not send the entire record: should block on reading the header */
server_in.write_cursor = 1;
EXPECT_ERROR_WITH_ERRNO(
s2n_negotiate_until_message(server, &blocked, SERVER_HELLO),
S2N_ERR_IO_BLOCKED);
EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_READ);

/* Do not send the entire record: should block on reading the fragment */
server_in.write_cursor = S2N_TLS_RECORD_HEADER_LENGTH + 10;
EXPECT_ERROR_WITH_ERRNO(
s2n_negotiate_until_message(server, &blocked, SERVER_HELLO),
S2N_ERR_IO_BLOCKED);
EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_READ);

/* Successfully read the rest of the record */
server_in.write_cursor = sizeof(sslv2_client_hello);
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
16 changes: 12 additions & 4 deletions tls/s2n_record_read.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,18 @@ int s2n_sslv2_record_header_parse(

S2N_ERROR_IF(s2n_stuffer_data_available(in) < S2N_TLS_RECORD_HEADER_LENGTH, S2N_ERR_BAD_MESSAGE);

/* The first bit of the length must be set to indicate SSLv2,
* so the raw fragment length must be adjusted.
* Revert the change afterwards in case we need to re-parse the header.
*/
in->blob.data[0] ^= S2N_TLS_SSLV2_HEADER_FLAG;
POSIX_GUARD(s2n_stuffer_read_uint16(in, fragment_length));
in->blob.data[0] |= S2N_TLS_SSLV2_HEADER_FLAG;

/* 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
* 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,8 +61,8 @@ 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(in), S2N_ERR_BAD_MESSAGE);
*fragment_length -= s2n_stuffer_data_available(in);

/*
* The first field of an SSLv2 ClientHello is the msg_type.
Expand All @@ -77,6 +84,7 @@ int s2n_sslv2_record_header_parse(

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

POSIX_GUARD(s2n_stuffer_reread(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

0 comments on commit edb3afb

Please sign in to comment.