summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortb <>2024-06-28 14:46:19 +0000
committertb <>2024-06-28 14:46:19 +0000
commite20d7daba569b993a10276f67b13602015592757 (patch)
treec397091725956bf38b38196bd78d9b65ce50ec56
parent652a2c127832401e38b41c6f3add4747e0e7157e (diff)
downloadopenbsd-e20d7daba569b993a10276f67b13602015592757.tar.gz
openbsd-e20d7daba569b993a10276f67b13602015592757.tar.bz2
openbsd-e20d7daba569b993a10276f67b13602015592757.zip
Fix SSL_select_next_proto()
SSL_select_next_proto() is already quite broken by its design: const in, non-const out, with the intention of pointing somewhere inside of the two input pointers. A length returned in an unsigned char (because, you know, the individual protocols are encoded in Pascal strings). Can't signal uailure either. It also has an unreachable public return code. Also, due to originally catering to NPN, this function opportunistically selects a protocol from the second input (client) parameters, which makes little sense for ALPN since that means the server falls back to a protocol it doesn't (want to) support. If there's no overlap, it's the callback's job to signal error to its caller for ALPN. As if that wasn't enough misdesign and bugs, the one we're concerned with here wasn't reported to us twice in ten years is that if you pass this API a zero-length (or a sufficiently malformed client protocol list), it would return a pointer pointing somewhere into the heap instead into one of the two input pointers. This pointer could then be interpreted as a Pascal string, resulting in an information disclosure of up to 255 bytes from the heap to the peer, or a crash. This can only happen for NPN (where it does happen in old python and node). A long time ago jsing removed NPN support from LibreSSL, because it had an utter garbage implementation and because it was practically unused. First it was already replaced by the somewhat less bad ALPN, and the only users were the always same language bindings that tend to use every feature they shouldn't use. There were a lot of complaints due to failing test cases in there, but in the end the decision turned out to be the right one: the consequence is that LibreSSL isn't vulnerable to CVE-2024-5535. Still, there is a bug here to fix. It is completely straightforward to do so. Rewrite this mess using CBS, preserving the current behavior. Also, we do not follow BoringSSL's renaming of the variables. It would result in confusing code in almost all alpn callbacks I've seen in the wild. The only exception is the accidental example of Qt. ok jsing
-rw-r--r--src/lib/libssl/ssl_lib.c83
1 files changed, 54 insertions, 29 deletions
diff --git a/src/lib/libssl/ssl_lib.c b/src/lib/libssl/ssl_lib.c
index d1b552d94f..406567b535 100644
--- a/src/lib/libssl/ssl_lib.c
+++ b/src/lib/libssl/ssl_lib.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: ssl_lib.c,v 1.323 2024/04/15 16:00:05 tb Exp $ */ 1/* $OpenBSD: ssl_lib.c,v 1.324 2024/06/28 14:46:19 tb Exp $ */
2/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) 2/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
3 * All rights reserved. 3 * All rights reserved.
4 * 4 *
@@ -1785,45 +1785,70 @@ LSSL_ALIAS(SSL_get_servername_type);
1785 * It returns either: 1785 * It returns either:
1786 * OPENSSL_NPN_NEGOTIATED if a common protocol was found, or 1786 * OPENSSL_NPN_NEGOTIATED if a common protocol was found, or
1787 * OPENSSL_NPN_NO_OVERLAP if the fallback case was reached. 1787 * OPENSSL_NPN_NO_OVERLAP if the fallback case was reached.
1788 *
1789 * XXX - the out argument points into server_list or client list and should
1790 * therefore really be const. We can't fix that without breaking the callers.
1788 */ 1791 */
1789int 1792int
1790SSL_select_next_proto(unsigned char **out, unsigned char *outlen, 1793SSL_select_next_proto(unsigned char **out, unsigned char *outlen,
1791 const unsigned char *server, unsigned int server_len, 1794 const unsigned char *server_list, unsigned int server_list_len,
1792 const unsigned char *client, unsigned int client_len) 1795 const unsigned char *client_list, unsigned int client_list_len)
1793{ 1796{
1794 unsigned int i, j; 1797 CBS client, client_proto, server, server_proto;
1795 const unsigned char *result; 1798
1796 int status = OPENSSL_NPN_UNSUPPORTED; 1799 *out = NULL;
1800 *outlen = 0;
1801
1802 /* First check that the client list is well-formed. */
1803 CBS_init(&client, client_list, client_list_len);
1804 if (!tlsext_alpn_check_format(&client))
1805 goto err;
1806
1807 /*
1808 * Use first client protocol as fallback. This is one way of doing NPN's
1809 * "opportunistic" protocol selection (see security considerations in
1810 * draft-agl-tls-nextprotoneg-04), and it is the documented behavior of
1811 * this API. For ALPN it's the callback's responsibility to fail on
1812 * OPENSSL_NPN_NO_OVERLAP.
1813 */
1814
1815 if (!CBS_get_u8_length_prefixed(&client, &client_proto))
1816 goto err;
1817
1818 *out = (unsigned char *)CBS_data(&client_proto);
1819 *outlen = CBS_len(&client_proto);
1820
1821 /* Now check that the server list is well-formed. */
1822 CBS_init(&server, server_list, server_list_len);
1823 if (!tlsext_alpn_check_format(&server))
1824 goto err;
1797 1825
1798 /* 1826 /*
1799 * For each protocol in server preference order, 1827 * Walk the server list and select the first protocol that appears in
1800 * see if we support it. 1828 * the client list.
1801 */ 1829 */
1802 for (i = 0; i < server_len; ) { 1830 while (CBS_len(&server) > 0) {
1803 for (j = 0; j < client_len; ) { 1831 if (!CBS_get_u8_length_prefixed(&server, &server_proto))
1804 if (server[i] == client[j] && 1832 goto err;
1805 memcmp(&server[i + 1], 1833
1806 &client[j + 1], server[i]) == 0) { 1834 CBS_init(&client, client_list, client_list_len);
1807 /* We found a match */ 1835
1808 result = &server[i]; 1836 while (CBS_len(&client) > 0) {
1809 status = OPENSSL_NPN_NEGOTIATED; 1837 if (!CBS_get_u8_length_prefixed(&client, &client_proto))
1810 goto found; 1838 goto err;
1839
1840 if (CBS_mem_equal(&client_proto,
1841 CBS_data(&server_proto), CBS_len(&server_proto))) {
1842 *out = (unsigned char *)CBS_data(&server_proto);
1843 *outlen = CBS_len(&server_proto);
1844
1845 return OPENSSL_NPN_NEGOTIATED;
1811 } 1846 }
1812 j += client[j];
1813 j++;
1814 } 1847 }
1815 i += server[i];
1816 i++;
1817 } 1848 }
1818 1849
1819 /* There's no overlap between our protocols and the server's list. */ 1850 err:
1820 result = client; 1851 return OPENSSL_NPN_NO_OVERLAP;
1821 status = OPENSSL_NPN_NO_OVERLAP;
1822
1823 found:
1824 *out = (unsigned char *) result + 1;
1825 *outlen = result[0];
1826 return (status);
1827} 1852}
1828LSSL_ALIAS(SSL_select_next_proto); 1853LSSL_ALIAS(SSL_select_next_proto);
1829 1854