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 CVE-2024-5535 #634

Merged
merged 9 commits into from
Aug 19, 2024
Merged
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

Changes between 8.4.0 and 8.5.0 [xx XXX xxxx]

*) 修复CVE-2024-5535

*) 修复CVE-2024-4741

*) 修复CVE-2024-4603
Expand Down
63 changes: 40 additions & 23 deletions ssl/ssl_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -3010,37 +3010,54 @@ int SSL_select_next_proto(unsigned char **out, unsigned char *outlen,
unsigned int server_len,
const unsigned char *client, unsigned int client_len)
{
unsigned int i, j;
const unsigned char *result;
int status = OPENSSL_NPN_UNSUPPORTED;
PACKET cpkt, csubpkt, spkt, ssubpkt;

if (!PACKET_buf_init(&cpkt, client, client_len)
|| !PACKET_get_length_prefixed_1(&cpkt, &csubpkt)
|| PACKET_remaining(&csubpkt) == 0) {
*out = NULL;
*outlen = 0;
return OPENSSL_NPN_NO_OVERLAP;
}

/*
* Set the default opportunistic protocol. Will be overwritten if we find
* a match.
*/
*out = (unsigned char *)PACKET_data(&csubpkt);
*outlen = (unsigned char)PACKET_remaining(&csubpkt);

/*
* For each protocol in server preference order, see if we support it.
*/
for (i = 0; i < server_len;) {
for (j = 0; j < client_len;) {
if (server[i] == client[j] &&
memcmp(&server[i + 1], &client[j + 1], server[i]) == 0) {
/* We found a match */
result = &server[i];
status = OPENSSL_NPN_NEGOTIATED;
goto found;
if (PACKET_buf_init(&spkt, server, server_len)) {
while (PACKET_get_length_prefixed_1(&spkt, &ssubpkt)) {
if (PACKET_remaining(&ssubpkt) == 0)
continue; /* Invalid - ignore it */
if (PACKET_buf_init(&cpkt, client, client_len)) {
while (PACKET_get_length_prefixed_1(&cpkt, &csubpkt)) {
if (PACKET_equal(&csubpkt, PACKET_data(&ssubpkt),
PACKET_remaining(&ssubpkt))) {
/* We found a match */
*out = (unsigned char *)PACKET_data(&ssubpkt);
*outlen = (unsigned char)PACKET_remaining(&ssubpkt);
return OPENSSL_NPN_NEGOTIATED;
}
}
/* Ignore spurious trailing bytes in the client list */
} else {
/* This should never happen */
return OPENSSL_NPN_NO_OVERLAP;
}
j += client[j];
j++;
}
i += server[i];
i++;
/* Ignore spurious trailing bytes in the server list */
}

/* There's no overlap between our protocols and the server's list. */
result = client;
status = OPENSSL_NPN_NO_OVERLAP;

found:
*out = (unsigned char *)result + 1;
*outlen = result[0];
return status;
/*
* There's no overlap between our protocols and the server's list. We use
* the default opportunistic protocol selected earlier
*/
return OPENSSL_NPN_NO_OVERLAP;
}

#ifndef OPENSSL_NO_NEXTPROTONEG
Expand Down
27 changes: 26 additions & 1 deletion ssl/statem/extensions_clnt.c
Original file line number Diff line number Diff line change
Expand Up @@ -1650,7 +1650,8 @@ int tls_parse_stoc_npn(SSL *s, PACKET *pkt, unsigned int context, X509 *x,
PACKET_data(pkt),
PACKET_remaining(pkt),
s->ctx->ext.npn_select_cb_arg) !=
SSL_TLSEXT_ERR_OK) {
SSL_TLSEXT_ERR_OK
|| selected_len == 0) {
SSLfatal(s, SSL_AD_HANDSHAKE_FAILURE, SSL_R_BAD_EXTENSION);
return 0;
}
Expand Down Expand Up @@ -1679,6 +1680,8 @@ int tls_parse_stoc_alpn(SSL *s, PACKET *pkt, unsigned int context, X509 *x,
size_t chainidx)
{
size_t len;
PACKET confpkt, protpkt;
int valid = 0;

/* We must have requested it. */
if (!s->s3.alpn_sent) {
Expand All @@ -1697,6 +1700,28 @@ int tls_parse_stoc_alpn(SSL *s, PACKET *pkt, unsigned int context, X509 *x,
SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
return 0;
}

/* It must be a protocol that we sent */
if (!PACKET_buf_init(&confpkt, s->ext.alpn, s->ext.alpn_len)) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return 0;
}
while (PACKET_get_length_prefixed_1(&confpkt, &protpkt)) {
if (PACKET_remaining(&protpkt) != len)
continue;
if (memcmp(PACKET_data(pkt), PACKET_data(&protpkt), len) == 0) {
/* Valid protocol found */
valid = 1;
break;
}
}

if (!valid) {
/* The protocol sent from the server does not match one we advertised */
SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
return 0;
}

OPENSSL_free(s->s3.alpn_selected);
s->s3.alpn_selected = OPENSSL_malloc(len);
if (s->s3.alpn_selected == NULL) {
Expand Down
3 changes: 2 additions & 1 deletion ssl/statem/extensions_srvr.c
Original file line number Diff line number Diff line change
Expand Up @@ -1566,9 +1566,10 @@ EXT_RETURN tls_construct_stoc_next_proto_neg(SSL *s, WPACKET *pkt,
return EXT_RETURN_FAIL;
}
s->s3.npn_seen = 1;
return EXT_RETURN_SENT;
}

return EXT_RETURN_SENT;
return EXT_RETURN_NOT_SENT;
}
#endif

Expand Down
27 changes: 26 additions & 1 deletion ssl/statem_ntls/ntls_extensions_clnt.c
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,8 @@ int tls_parse_stoc_npn_ntls(SSL *s, PACKET *pkt, unsigned int context, X509 *x,
PACKET_data(pkt),
PACKET_remaining(pkt),
s->ctx->ext.npn_select_cb_arg) !=
SSL_TLSEXT_ERR_OK) {
SSL_TLSEXT_ERR_OK
|| selected_len == 0) {
SSLfatal_ntls(s, SSL_AD_HANDSHAKE_FAILURE, SSL_R_BAD_EXTENSION);
return 0;
}
Expand Down Expand Up @@ -673,6 +674,8 @@ int tls_parse_stoc_alpn_ntls(SSL *s, PACKET *pkt, unsigned int context, X509 *x,
size_t chainidx)
{
size_t len;
PACKET confpkt, protpkt;
int valid = 0;

/* We must have requested it. */
if (!s->s3.alpn_sent) {
Expand All @@ -691,6 +694,28 @@ int tls_parse_stoc_alpn_ntls(SSL *s, PACKET *pkt, unsigned int context, X509 *x,
SSLfatal_ntls(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
return 0;
}

/* It must be a protocol that we sent */
if (!PACKET_buf_init(&confpkt, s->ext.alpn, s->ext.alpn_len)) {
SSLfatal_ntls(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return 0;
}
while (PACKET_get_length_prefixed_1(&confpkt, &protpkt)) {
if (PACKET_remaining(&protpkt) != len)
continue;
if (memcmp(PACKET_data(pkt), PACKET_data(&protpkt), len) == 0) {
/* Valid protocol found */
valid = 1;
break;
}
}

if (!valid) {
/* The protocol sent from the server does not match one we advertised */
SSLfatal_ntls(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
return 0;
}

OPENSSL_free(s->s3.alpn_selected);
s->s3.alpn_selected = OPENSSL_malloc(len);
if (s->s3.alpn_selected == NULL) {
Expand Down
3 changes: 2 additions & 1 deletion ssl/statem_ntls/ntls_extensions_srvr.c
Original file line number Diff line number Diff line change
Expand Up @@ -587,9 +587,10 @@ EXT_RETURN tls_construct_stoc_next_proto_neg_ntls(SSL *s, WPACKET *pkt,
return EXT_RETURN_FAIL;
}
s->s3.npn_seen = 1;
return EXT_RETURN_SENT;
}

return EXT_RETURN_SENT;
return EXT_RETURN_NOT_SENT;
}
#endif

Expand Down
6 changes: 6 additions & 0 deletions test/helpers/handshake.c
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,12 @@ static int parse_protos(const char *protos, unsigned char **out, size_t *outlen)

len = strlen(protos);

if (len == 0) {
*out = NULL;
*outlen = 0;
return 1;
}

/* Should never have reuse. */
if (!TEST_ptr_null(*out)
/* Test values are small, so we omit length limit checks. */
Expand Down
73 changes: 73 additions & 0 deletions test/recipes/70-test_npn.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#! /usr/bin/env perl
# Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the Apache License 2.0 (the "License"). You may not use
# this file except in compliance with the License. You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html

use strict;
use OpenSSL::Test qw/:DEFAULT cmdstr srctop_file/;
use OpenSSL::Test::Utils;

use TLSProxy::Proxy;

my $test_name = "test_npn";
setup($test_name);

plan skip_all => "TLSProxy isn't usable on $^O"
if $^O =~ /^(VMS)$/;

plan skip_all => "$test_name needs the dynamic engine feature enabled"
if disabled("engine") || disabled("dynamic-engine");

plan skip_all => "$test_name needs the sock feature enabled"
if disabled("sock");

plan skip_all => "$test_name needs NPN enabled"
if disabled("nextprotoneg");

plan skip_all => "$test_name needs TLSv1.2 enabled"
if disabled("tls1_2");

my $proxy = TLSProxy::Proxy->new(
undef,
cmdstr(app(["openssl"]), display => 1),
srctop_file("apps", "server.pem"),
(!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE})
);

$proxy->start() or plan skip_all => "Unable to start up Proxy for tests";
plan tests => 1;

my $npnseen = 0;

# Test 1: Check sending an empty NextProto message from the client works. This is
# valid as per the spec, but OpenSSL does not allow you to send it.
# Therefore we must be prepared to receive such a message but we cannot
# generate it except via TLSProxy
$proxy->clear();
$proxy->filter(\&npn_filter);
$proxy->clientflags("-nextprotoneg foo -no_tls1_3");
$proxy->serverflags("-nextprotoneg foo");
$proxy->start();
ok($npnseen && TLSProxy::Message->success(), "Empty NPN message");

sub npn_filter
{
my $proxy = shift;
my $message;

# The NextProto message always appears in flight 2
return if $proxy->flight != 2;

foreach my $message (@{$proxy->message_list}) {
if ($message->mt == TLSProxy::Message::MT_NEXT_PROTO) {
# Our TLSproxy NextProto message support doesn't support parsing of
# the message. If we repack it just creates an empty NextProto
# message - which is exactly the scenario we want to test here.
$message->repack();
$npnseen = 1;
}
}
}
Loading
Loading